diff --git a/Makefile b/Makefile index f6dc0999..e91577ea 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,9 @@ static_assets: go get github.com/GeertJohan/go.rice/rice && \ cd ./handlers && \ rm -f rice-box.go && \ + rice embed-go && \ + cd ../helper/assethelper && \ + rm -f rice-box.go && \ rice embed-go compile_backend: diff --git a/go.mod b/go.mod index fc85bb98..0ab29ecd 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,6 @@ require ( github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55 // indirect github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.0.0-20180613044413-d6898124de91 // indirect - github.com/markbates/pkger v0.17.0 github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.3 // indirect github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff // indirect @@ -51,6 +50,7 @@ require ( github.com/sirupsen/logrus v1.4.2 // indirect github.com/speza/casbin-bolt-adapter v0.0.0-20200607190553-9ea048cf76dc github.com/src-d/gcfg v1.3.0 // indirect + github.com/stretchr/testify v1.4.0 // indirect github.com/xanzy/ssh-agent v0.2.0 // indirect golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be diff --git a/go.sum b/go.sum index 3d0883c5..746f6f75 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,6 @@ github.com/gaia-pipeline/protobuf v0.0.0-20180812091451-7be8a901b55a h1:/5XAmdAy github.com/gaia-pipeline/protobuf v0.0.0-20180812091451-7be8a901b55a/go.mod h1:H0w7MofSuW53Nz7kesnBdVkvr437flf5B7D9Lcsb+lQ= github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= @@ -106,8 +104,6 @@ github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8 github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= github.com/labstack/gommon v0.0.0-20180613044413-d6898124de91 h1:6R2WAx0PYNGtK2ZED9xKlTGEha51GsCFZijom+NMGEk= github.com/labstack/gommon v0.0.0-20180613044413-d6898124de91/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= -github.com/markbates/pkger v0.17.0 h1:RFfyBPufP2V6cddUyyEVSHBpaAnM1WzaMNyqomeT+iY= -github.com/markbates/pkger v0.17.0/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= diff --git a/handlers/handler.go b/handlers/handler.go index f92cc34b..36c7a525 100644 --- a/handlers/handler.go +++ b/handlers/handler.go @@ -1,7 +1,6 @@ package handlers import ( - "log" "net/http" rice "github.com/GeertJohan/go.rice" @@ -12,8 +11,6 @@ import ( "github.com/gaia-pipeline/gaia/handlers/providers/pipelines" "github.com/gaia-pipeline/gaia/handlers/providers/workers" "github.com/gaia-pipeline/gaia/helper/rolehelper" - "github.com/gaia-pipeline/gaia/security/rbac" - "github.com/gaia-pipeline/gaia/services" ) var ( @@ -28,23 +25,13 @@ func (s *GaiaHandler) InitHandlers(e *echo.Echo) error { // --- Register handlers at echo instance --- - store, err := services.StorageService() - if err != nil { - log.Fatal(err) - } - - enforcerSvc, err := rbac.NewEnforcerSvc(store.CasbinStore()) - if err != nil { - log.Fatal(err) - } - // Standard API router group. apiGrp := e.Group(p) // Auth API router group. apiAuthGrp := e.Group(p, authMiddleware(&AuthConfig{ RoleCategories: rolehelper.DefaultUserRoles, - rbacEnforcer: enforcerSvc, + rbacEnforcer: s.deps.RBACService, })) // Endpoints for Gaia primary instance @@ -103,7 +90,7 @@ func (s *GaiaHandler) InitHandlers(e *echo.Echo) error { // RBAC rbacHandler := rbacHandler{ - svc: enforcerSvc, + svc: s.deps.RBACService, } // RBAC - Management apiAuthGrp.GET("rbac/roles", rbacHandler.getAllRoles) diff --git a/handlers/rbac.go b/handlers/rbac.go index 417c93d9..7c7e2dcc 100644 --- a/handlers/rbac.go +++ b/handlers/rbac.go @@ -1,9 +1,11 @@ package handlers import ( - "github.com/gaia-pipeline/gaia/security/rbac" - "github.com/labstack/echo" "net/http" + + "github.com/labstack/echo" + + "github.com/gaia-pipeline/gaia/security/rbac" ) type rbacHandler struct { diff --git a/handlers/service.go b/handlers/service.go index a2da087b..a4da36e6 100644 --- a/handlers/service.go +++ b/handlers/service.go @@ -2,6 +2,7 @@ package handlers import ( "github.com/gaia-pipeline/gaia/security" + "github.com/gaia-pipeline/gaia/security/rbac" "github.com/gaia-pipeline/gaia/workers/pipeline" "github.com/gaia-pipeline/gaia/workers/scheduler/service" ) @@ -11,6 +12,7 @@ type Dependencies struct { Scheduler service.GaiaScheduler PipelineService pipeline.Service Certificate security.CAAPI + RBACService rbac.Service } // GaiaHandler defines handler functions throughout Gaia. diff --git a/helper/assethelper/helper.go b/helper/assethelper/helper.go new file mode 100644 index 00000000..c38a9598 --- /dev/null +++ b/helper/assethelper/helper.go @@ -0,0 +1,32 @@ +package assethelper + +import ( + rice "github.com/GeertJohan/go.rice" +) + +func loadStaticFile(filename string) (string, error) { + box, err := rice.FindBox("../../static") + if err != nil { + return "", err + } + filestr, err := box.String(filename) + if err != nil { + return "", err + } + return filestr, nil +} + +// LoadRBACBuiltinPolicy loads the builtin rbac-policy.csv +func LoadRBACBuiltinPolicy() (string, error) { + return loadStaticFile("rbac-policy.csv") +} + +// LoadRBACAPIMappings loads the rbac-api-mappings.yml +func LoadRBACAPIMappings() (string, error) { + return loadStaticFile("rbac-api-mappings.yml") +} + +// LoadRBACModel loads the rbac-model.conf +func LoadRBACModel() (string, error) { + return loadStaticFile("rbac-model.conf") +} diff --git a/security/rbac/endpoint_enforcer.go b/security/rbac/endpoint_enforcer.go index cd3f0c94..de32ec9e 100644 --- a/security/rbac/endpoint_enforcer.go +++ b/security/rbac/endpoint_enforcer.go @@ -13,7 +13,7 @@ type EndpointEnforcer interface { } // Enforce uses the echo.Context to enforce RBAC. Uses the rbacapiMappings to apply policies to specific endpoints. -func (e *EnforcerService) Enforce(username, method, path string, params map[string]string) (bool, error) { +func (e *enforcerService) Enforce(username, method, path string, params map[string]string) (bool, error) { group := e.rbacapiMappings endpoint, ok := group.Endpoints[path] diff --git a/security/rbac/endpoint_enforcer_test.go b/security/rbac/endpoint_enforcer_test.go index 9200f3c2..ab169cb1 100644 --- a/security/rbac/endpoint_enforcer_test.go +++ b/security/rbac/endpoint_enforcer_test.go @@ -46,7 +46,7 @@ func Test_EnforcerService_Enforce_ValidEnforcement(t *testing.T) { gaia.Cfg = nil }() - svc := EnforcerService{ + svc := enforcerService{ enforcer: &mockEnforcer{}, rbacapiMappings: mappings, } @@ -64,7 +64,7 @@ func Test_EnforcerService_Enforce_FailedEnforcement(t *testing.T) { gaia.Cfg = nil }() - svc := EnforcerService{ + svc := enforcerService{ enforcer: &mockEnforcer{}, rbacapiMappings: mappings, } @@ -82,7 +82,7 @@ func Test_EnforcerService_Enforce_ErrorEnforcement(t *testing.T) { gaia.Cfg = nil }() - svc := EnforcerService{ + svc := enforcerService{ enforcer: &mockEnforcer{}, rbacapiMappings: mappings, } @@ -100,7 +100,7 @@ func Test_EnforcerService_Enforce_EndpointParamMissing(t *testing.T) { gaia.Cfg = nil }() - svc := EnforcerService{ + svc := enforcerService{ enforcer: &mockEnforcer{}, rbacapiMappings: mappings, } diff --git a/security/rbac/service.go b/security/rbac/service.go index 968335e5..c1a28ad7 100644 --- a/security/rbac/service.go +++ b/security/rbac/service.go @@ -2,20 +2,19 @@ package rbac import ( "errors" - "io/ioutil" "log" "github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2/model" "github.com/casbin/casbin/v2/persist" - "github.com/markbates/pkger" "gopkg.in/yaml.v2" "github.com/gaia-pipeline/gaia" + "github.com/gaia-pipeline/gaia/helper/assethelper" ) type ( - // RoleRule represents a Casbins role rule line in the format we expect. + // RoleRule represents a Casbin role rule line in the format we expect. RoleRule struct { Namespace string Action string @@ -25,6 +24,7 @@ type ( // Service wraps the Casbin enforcer and performs all actions we require to manage and use RBAC functions. Service interface { + EndpointEnforcer AddRole(role string, roleRules []RoleRule) error DeleteRole(role string) error GetAllRoles() []string @@ -34,15 +34,15 @@ type ( DetachRole(username string, role string) error } - // EnforcerService implements the Service interface. - EnforcerService struct { + enforcerService struct { + adapter persist.BatchAdapter enforcer casbin.IEnforcer rbacapiMappings gaia.RBACAPIMappings } ) // NewEnforcerSvc creates a new EnforcerService. -func NewEnforcerSvc(adapter persist.BatchAdapter) (*EnforcerService, error) { +func NewEnforcerSvc(adapter persist.BatchAdapter) (Service, error) { model, err := loadModel() if err != nil { return nil, err @@ -59,24 +59,19 @@ func NewEnforcerSvc(adapter persist.BatchAdapter) (*EnforcerService, error) { return nil, err } - return &EnforcerService{ + return &enforcerService{ enforcer: enforcer, rbacapiMappings: rbacapiMappings, }, nil } func loadModel() (model.Model, error) { - modelFile, err := pkger.Open("/security/rbac/rbac-model.conf") + modelStr, err := assethelper.LoadRBACModel() if err != nil { return nil, err } - modelBts, err := ioutil.ReadAll(modelFile) - if err != nil { - return nil, err - } - - model, err := model.NewModelFromString(string(modelBts)) + model, err := model.NewModelFromString(modelStr) if err != nil { return nil, err } @@ -85,18 +80,13 @@ func loadModel() (model.Model, error) { } func loadAPIMappings() (gaia.RBACAPIMappings, error) { - file, err := pkger.Open("/security/rbac/rbac-api-mappings.yml") - if err != nil { - return gaia.RBACAPIMappings{}, err - } - - bts, err := ioutil.ReadAll(file) + mappings, err := assethelper.LoadRBACAPIMappings() if err != nil { return gaia.RBACAPIMappings{}, err } var rbacapiMappings gaia.RBACAPIMappings - if err := yaml.Unmarshal(bts, &rbacapiMappings); err != nil { + if err := yaml.Unmarshal([]byte(mappings), &rbacapiMappings); err != nil { return gaia.RBACAPIMappings{}, err } @@ -104,7 +94,7 @@ func loadAPIMappings() (gaia.RBACAPIMappings, error) { } // DeleteRole deletes a role. -func (e *EnforcerService) DeleteRole(role string) error { +func (e *enforcerService) DeleteRole(role string) error { exists, err := e.enforcer.DeleteRole(role) if !exists { return errors.New("role does not exist") @@ -113,7 +103,7 @@ func (e *EnforcerService) DeleteRole(role string) error { } // AddRole adds a role. -func (e *EnforcerService) AddRole(role string, roleRules []RoleRule) error { +func (e *enforcerService) AddRole(role string, roleRules []RoleRule) error { rules := [][]string{} for _, p := range roleRules { r := []string{role, p.Namespace, p.Action, p.Resource, p.Effect} @@ -132,22 +122,22 @@ func (e *EnforcerService) AddRole(role string, roleRules []RoleRule) error { } // GetAllRoles gets all roles. -func (e *EnforcerService) GetAllRoles() []string { +func (e *enforcerService) GetAllRoles() []string { return e.enforcer.GetAllRoles() } // GetUserAttachedRoles gets all roles attached to a specific user. -func (e *EnforcerService) GetUserAttachedRoles(username string) ([]string, error) { +func (e *enforcerService) GetUserAttachedRoles(username string) ([]string, error) { return e.enforcer.GetRolesForUser(username) } // GetRoleAttachedUsers get all users attached to a specific role. -func (e *EnforcerService) GetRoleAttachedUsers(role string) ([]string, error) { +func (e *enforcerService) GetRoleAttachedUsers(role string) ([]string, error) { return e.enforcer.GetUsersForRole(role) } // AttachRole attaches a role to a user. -func (e *EnforcerService) AttachRole(username string, role string) error { +func (e *enforcerService) AttachRole(username string, role string) error { if _, err := e.enforcer.AddRoleForUser(username, role); err != nil { return err } @@ -155,7 +145,7 @@ func (e *EnforcerService) AttachRole(username string, role string) error { } // DetachRole detaches a role from a user. -func (e *EnforcerService) DetachRole(username string, role string) error { +func (e *enforcerService) DetachRole(username string, role string) error { exists, err := e.enforcer.DeleteRoleForUser(username, role) if err != nil { return err diff --git a/server/server.go b/server/server.go index 11ca05ad..1572a1e6 100644 --- a/server/server.go +++ b/server/server.go @@ -19,6 +19,7 @@ import ( "github.com/gaia-pipeline/gaia/handlers" "github.com/gaia-pipeline/gaia/plugin" "github.com/gaia-pipeline/gaia/security" + "github.com/gaia-pipeline/gaia/security/rbac" "github.com/gaia-pipeline/gaia/services" "github.com/gaia-pipeline/gaia/workers/agent" "github.com/gaia-pipeline/gaia/workers/pipeline" @@ -249,11 +250,17 @@ func Start() (err error) { Scheduler: schedulerService, }) + enforcerSvc, err := rbac.NewEnforcerSvc(store.CasbinStore()) + if err != nil { + return err + } + // Initialize handlers handlerService := handlers.NewGaiaHandler(handlers.Dependencies{ Scheduler: schedulerService, PipelineService: pipelineService, Certificate: ca, + RBACService: enforcerSvc, }) err = handlerService.InitHandlers(echoInstance) diff --git a/security/rbac/rbac-api-mappings.yml b/static/rbac-api-mappings.yml similarity index 100% rename from security/rbac/rbac-api-mappings.yml rename to static/rbac-api-mappings.yml diff --git a/security/rbac/rbac-model.conf b/static/rbac-model.conf similarity index 100% rename from security/rbac/rbac-model.conf rename to static/rbac-model.conf diff --git a/security/rbac/rbac-policy.csv b/static/rbac-policy.csv similarity index 100% rename from security/rbac/rbac-policy.csv rename to static/rbac-policy.csv diff --git a/store/store.go b/store/store.go index 2d3cbe63..cc579e6f 100644 --- a/store/store.go +++ b/store/store.go @@ -3,16 +3,15 @@ package store import ( "encoding/binary" "fmt" - "io/ioutil" "path/filepath" "time" "github.com/casbin/casbin/v2/persist" bolt "github.com/coreos/bbolt" - "github.com/markbates/pkger" "github.com/speza/casbin-bolt-adapter" "github.com/gaia-pipeline/gaia" + "github.com/gaia-pipeline/gaia/helper/assethelper" "github.com/gaia-pipeline/gaia/security" ) @@ -128,15 +127,8 @@ func (s *BoltStore) Init(dataPath string) error { // TODO: Having Casbin stuff here doesn't sit quite right with me (especially loading a file here). // Unfortunately we need to re-use the open bolt database for the adapter though. - rbacPolicyFile, err := pkger.Open("/security/rbac/rbac-policy.csv") - if err != nil { - return err - } - rbacPolicyBts, err := ioutil.ReadAll(rbacPolicyFile) - if err != nil { - return err - } - casbinAdapter, err := boltadapter.NewAdapter(db, "casbin-policies", string(rbacPolicyBts)) + builtinPolicy, _ := assethelper.LoadRBACBuiltinPolicy() + casbinAdapter, err := boltadapter.NewAdapter(db, "casbin-policies", builtinPolicy) if err != nil { return err }