diff --git a/rbac/enforcer.go b/rbac/enforcer.go new file mode 100644 index 000000000..8c256724d --- /dev/null +++ b/rbac/enforcer.go @@ -0,0 +1,73 @@ +package rbac + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + + "github.com/open-policy-agent/opa/sdk" + sdktest "github.com/open-policy-agent/opa/sdk/test" +) + +type ABACPolicyEnforcer struct { + S *sdk.OPA + CTX context.Context + Server *sdktest.Server +} + +func NewABACPolicyEnforcer() (*ABACPolicyEnforcer, error) { + + ctx := context.Background() + + policy, err := ioutil.ReadFile("example_policy.rego") + if err != nil { + return nil, err + } + policyS := string(policy) + server, err := sdktest.NewServer(sdktest.MockBundle("/bundles/bundle.tar.gz", map[string]string{ + "example.rego": policyS, + })) + if err != nil { + return nil, err + } + config := []byte(fmt.Sprintf(`{ + "services": { + "test": { + "url": %q + } + }, + "bundles": { + "test": { + "resource": "/bundles/bundle.tar.gz" + } + }, + "decision_logs": { + "console": true + } + }`, server.URL())) + opa, err := sdk.New(ctx, sdk.Options{ + Config: bytes.NewReader(config), + }) + if err != nil { + return nil, err + } + + return &ABACPolicyEnforcer{ + S: opa, + CTX: ctx, + Server: server, + }, nil + +} + +func readPolicyFile(p string) ([]byte, error) { + policyData, err := ioutil.ReadFile(p) + + if err != nil { + return nil, err + } + + return policyData, nil + +} diff --git a/rbac/enforcer_test.go b/rbac/enforcer_test.go new file mode 100644 index 000000000..4183474e4 --- /dev/null +++ b/rbac/enforcer_test.go @@ -0,0 +1,37 @@ +package rbac_test + +import ( + "testing" + + "github.com/featurebasedb/featurebase/v3/rbac" + "github.com/open-policy-agent/opa/sdk" +) + +func TestCreateABACPolicyEnforcer(t *testing.T) { + enf, err := rbac.NewABACPolicyEnforcer() + if err != nil { + t.Errorf("Error creating ABACPolicyEnforcer: %s", err) + } + + defer enf.Server.Stop() + defer enf.S.Stop(enf.CTX) + + result, err := enf.S.Decision(enf.CTX, sdk.DecisionOptions{Path: "/featurebase/authz/allow", Input: map[string]interface{}{"open": "sesame"}}) + if err != nil { + t.Errorf("Error testing OPA decision: %s", err) + } + if result.Result.(bool) != false { + t.Errorf("Result was not false!") + } + + result, err = enf.S.Decision(enf.CTX, sdk.DecisionOptions{Path: "/featurebase/authz/allow", Input: map[string]interface{}{"method": "SELECT", "path": "index_a", "user": "fletcher"}}) + + if err != nil { + t.Errorf("Error testing OPA decision: %s", err) + } + + if result.Result.(bool) != true { + t.Errorf("Result was not true!") + } + +} diff --git a/rbac/errors.go b/rbac/errors.go new file mode 100644 index 000000000..d1f0efadd --- /dev/null +++ b/rbac/errors.go @@ -0,0 +1,11 @@ +package rbac + +import "fmt" + +type RBACError struct { + err string +} + +func (e RBACError) Error() string { + return fmt.Sprintf("RBACError: %s", e.err) +} diff --git a/rbac/example.yaml b/rbac/example.yaml index bc4d1a95c..03bb72faf 100644 --- a/rbac/example.yaml +++ b/rbac/example.yaml @@ -1,8 +1,3 @@ ---- -user: - name: fletcher - email: fletcher.haynes@featurebase.com - --- group: name: employees diff --git a/rbac/example_policy.rego b/rbac/example_policy.rego new file mode 100644 index 000000000..89522731d --- /dev/null +++ b/rbac/example_policy.rego @@ -0,0 +1,10 @@ +package featurebase.authz + +default allow = false + +allow { + input.method = "SELECT" + input.path = "index_a" + input.user = "fletcher" + +} \ No newline at end of file diff --git a/rbac/opa.go b/rbac/opa.go new file mode 100644 index 000000000..0b3126179 --- /dev/null +++ b/rbac/opa.go @@ -0,0 +1 @@ +package rbac diff --git a/rbac/user.go b/rbac/user.go index b2115167d..b353df81b 100644 --- a/rbac/user.go +++ b/rbac/user.go @@ -1,6 +1,12 @@ package rbac -import "google.golang.org/genproto/googleapis/type/datetime" +import ( + "fmt" + "io/ioutil" + "time" + + "gopkg.in/yaml.v2" +) type FBUserStatus int32 @@ -11,9 +17,42 @@ const ( ) type FBUser struct { - Username string - Email string - Created datetime.DateTime - LastActivity datetime.DateTime + Username string `yaml:"username"` + Email string `yaml:"email"` + Created time.Time + LastActivity time.Time Status FBUserStatus } + +func NewFBUser(username, email string) FBUser { + newUser := FBUser{ + Username: username, + Email: email, + Created: time.Now(), + LastActivity: time.Now(), + Status: Active, + } + return newUser +} +func LoadUsersFromYAML(p string) ([]FBUser, error) { + usersData := make(map[string][]map[string]string) + users := make([]FBUser, 0) + data, err := ioutil.ReadFile(p) + if err != nil { + return nil, err + } + err = yaml.Unmarshal(data, usersData) + if err != nil { + return nil, err + } + fmt.Printf("Users are: %v", usersData) + for _, user := range usersData["users"] { + newUser := FBUser{ + Username: user["name"], + Email: user["email"], + } + users = append(users, newUser) + } + return users, nil + +} diff --git a/rbac/user_test.go b/rbac/user_test.go new file mode 100644 index 000000000..796db9d94 --- /dev/null +++ b/rbac/user_test.go @@ -0,0 +1,27 @@ +package rbac_test + +import ( + "testing" + + "github.com/featurebasedb/featurebase/v3/rbac" +) + +func TestCreateUserFromYAML(t *testing.T) { + users, err := rbac.LoadUsersFromYAML("users.yaml") + if err != nil { + t.Errorf("Error loading test users from YAML: %s", err) + } + + if len(users) != 1 { + t.Errorf("Expected 1 user to be loaded from YAML. Got %d", len(users)) + } + + if users[0].Username != "fletcher" { + t.Errorf("Expected test user username to be fletcher. Got %s", users[0].Username) + } + + if users[0].Email != "fletcher.haynes@featurebase.com" { + t.Errorf("Expected test user email to be fletcher.haynes@featurebase.com. Got %s", users[0].Email) + } + +} diff --git a/rbac/users.yaml b/rbac/users.yaml new file mode 100644 index 000000000..67392acd0 --- /dev/null +++ b/rbac/users.yaml @@ -0,0 +1,5 @@ +--- +users: + - name: fletcher + email: fletcher.haynes@featurebase.com + \ No newline at end of file