-
Notifications
You must be signed in to change notification settings - Fork 243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RBAC using Casbin #256
RBAC using Casbin #256
Conversation
# Conflicts: # handlers/handler.go
- Better error handling - Renaming of some properties - Renaming of API mappings file - Use keyMatch in rbac-model.conf to allow wildcards - Fix import order - Rename mapping endpoint - Remove unused bolt bucket
* Use rice instead of pkger * Move enforcer instantiation to server.go * Fix makefile issue
Ok, so this is getting there now. This change introduces Casbin to enforce RBAC. We have a policy model that is defined like so (see static/rbac-model.conf). Namespace = pipelines, secrets, etc. We use manage a standard set of policy rules loaded in on server start ( static/rbac-policy.csv). Summary:
// RBAC - Management
TODO:
|
static/rbac-policy.csv
Outdated
# Policy format: | ||
# p, <role>, <namespace>, <action>, <resource>, <effect = allow/deny> | ||
|
||
p, role:admin, pipelines, get, *, allow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If role:admin
is assumed to access all, you can just write p, role:admin, *, *, *, allow
one rule to allow all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! That would definitely be handy here!
|
||
import ( | ||
"errors" | ||
"github.com/casbin/casbin/v2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort the imports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I agree with the approach. Well done. :) Just have a few remarks regarding the code itself and a tiny bit of structure in storage.
return true, nil | ||
} | ||
|
||
splitAction := strings.Split(perm, "/") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will we never have something like worker/pipeline/create
? When the namespace can be multiple embedded ones for clarity? Not saying we should, I'm just wondering if we should rather just check the last /
and everything after that is the action. Since the namespace could be defined as embedded namespaces. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm that's an interesting thought. We should probably handle it you're right.
Unless we name it something like worker::pipeline/create
? Actually I think worker/pipeline::create
maybe even looks nicer?
My main thoughts are that we should seperate the namespace
and action
with something unique (in this case /
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A single colon is fine actually, I think. No need to bring PHP into this. :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PHP.... Java... anything else? :P
namespace := splitAction[0] | ||
action := splitAction[1] | ||
|
||
fullResource := "*" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that we are allow all by default? I think that should be deny by default, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we need to use *
for endpoints where there isn't a resource id specified in the api-mappings yaml. Right now that is something like secrets/create
that doesn't need the enforcement of a resource id on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, why isn't there a secrets and why doesn't that need enforcement? :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you have misunderstood mate! We have permissions on secrets. I just mean that secrets/create
doesn't have resource (a key ID) enforced because you haven't created something yet!
So:
not specified
< cannot create secrets
p, secrets, create, *, deny
< cannot create secrets
p, secrets, create, *, allow
< can create secrets
security/rbac/service.go
Outdated
func loadModel() (model.Model, error) { | ||
modelStr, err := assethelper.LoadRBACModel() | ||
if err != nil { | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add logging here as well, and all the rest of the errors. The more messages we get the more we know what went wrong. Pass in the logger as a dependency so you can use it with a method receiver.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WIll go through and do this as I tidy up mate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to add logging at the caller level. So in this case it would be where we call NewEnforcerSvc
. The rest of the logging I will add into the handler code, etc.
One thing I will do is wrap the errors though to give more context 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's good as well. High level logging is fine but that logging wraps at least 3 or more different errors. If even one of them is similar we'll have no idea where the error happened. Wrapping is a good solution to that. Love the new error system. 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where’s a good old Java stacktrace when you need it! Haha
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bwuhahaha, pssss, not so loud.. The other gophers might hear you. 😂😂😂
security/rbac/service.go
Outdated
return gaia.RBACAPIMappings{}, err | ||
} | ||
|
||
var rbacapiMappings gaia.RBACAPIMappings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declare this above and then return it.
security/rbac/service.go
Outdated
} | ||
|
||
var rbacapiMappings gaia.RBACAPIMappings | ||
if err := yaml.Unmarshal([]byte(mappings), &rbacapiMappings); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should lock this mapping down for tempering with UnmarshalStrict. That will throw errors in case of duplicate fields and mappings which do not have corresponding structs.
return err | ||
} | ||
if !exists { | ||
return errors.New("role does not exists for user") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*exist
helper/assethelper/helper.go
Outdated
|
||
// LoadRBACBuiltinPolicy loads the builtin rbac-policy.csv | ||
func LoadRBACBuiltinPolicy() (string, error) { | ||
return loadStaticFile("rbac-policy.csv") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put the file names into constants.
* Updated our bolt dependency (the adapter is supporting latest - so makes sense to bump us too) * Log errors in handlers and correct wrap errors * Improve static rbac-policy.csv
Just to top it off @Skarlso circle is still using Go 1.12 so won’t compile the error wrapping! |
Yes. Right now circleci is failing until we can update it and push a new image. |
* We now load in a different file format at start up for the RBAC api mappings. This one is more flexible, and I believe more user friendly (see the comment in the .yml file). Once loaded, we still have an O(1) lookup available for the endpoints :) * Moved some models out of Gaia into rbac as they we only being used internally by the rbac package.
* Split the settings store interface out into its own (but nested in store.GaiaStore) * Added unit tests for settings.go and improved DI on existing ones * Created a new SettingsHandler to encapsulate the store dependency * Add ability to save RBAC enabled state into database * RBAC service is switched to EnforcerService or NoOpService depeneding on enabled state * RBAC can be enabled or disabled with cmd flag `-rbac-enabled=true/false`
Add RBAC settings flag (enabled/disabled) and some improvements * Split the settings store interface out into its own (but nested in store.GaiaStore) * Added unit tests for settings.go and improved DI on existing ones * Created a new SettingsHandler to encapsulate the store dependency * Add ability to save RBAC enabled state into database * RBAC service is switched to EnforcerService or NoOpService depending on enabled state * RBAC can be enabled or disabled with cmd flag `-rbac-enabled=true/false`
* Fixed issues with missing or incorrect rbac-api-mappings.yml * Started some acceptance tests for RBAC (probably to be finished outside this PR - its a lot of work) * Added some tests for the content of the rbac-api-mappings.yml to alert us to any future change * Endpoint Enforce now returns a PermissionDenied error
# Conflicts: # go.mod # go.sum
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #256 +/- ##
==========================================
+ Coverage 62.33% 62.80% +0.47%
==========================================
Files 46 51 +5
Lines 3746 4001 +255
==========================================
+ Hits 2335 2513 +178
- Misses 1027 1092 +65
- Partials 384 396 +12 ☔ View full report in Codecov by Sentry. |
|
||
// SettingsStore is the interface that defines methods needed to save settings config into the store. | ||
type SettingsStore interface { | ||
SettingsPut(config *gaia.StoreConfig) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't an RBACPut be a SettingsPut? Why are they two different functions? An interface shouldn't be explicit. It should be generic mostly. If there is a settingsRbacPut that should most likely be either a more specialised interface or a concrete implementation of settings put.
I see that it's putting / getting an RBACConfig instead of a StoreConfig, but that also means that it can't be a SettingsStore.
What I mean is, either make rbac use settingsput or have a separate interface. :)
# Conflicts: # handlers/handler.go # handlers/hook_test.go # handlers/providers/pipelines/settings.go # handlers/settings_test.go
# Conflicts: # handlers/settings.go # handlers/settings_test.go
Casbin rbac settings
Testing was successful on the deployed instance! Well done @speza! Thanks for this awesome work and effort you've put into this and your perseverance. :) Fantastic work! 👍 |
WIP