Skip to content
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

PTCI-830: Add FilterCompartmentPerm/Feat, GetCurrUserCompartments to main (v1) branch #43

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.17
require (
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/infobloxopen/atlas-app-toolkit v1.1.2
github.com/infobloxopen/atlas-claims v1.0.0
github.com/infobloxopen/atlas-claims v1.1.2
github.com/infobloxopen/seal v0.2.3
github.com/open-policy-agent/opa v0.37.2
github.com/sirupsen/logrus v1.8.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/infobloxopen/atlas-app-toolkit v1.1.2 h1:bB7vWc2mJnqmk+RU594L1JM8fJOQMu6MHy3Uglo0SUQ=
github.com/infobloxopen/atlas-app-toolkit v1.1.2/go.mod h1:vd1L67El5az4iEwBK5wqDt+VRDNUpDtVqJCYftnJ8S0=
github.com/infobloxopen/atlas-claims v1.0.0 h1:uGwFxbEGDZSql3ePeH/z/TA14IInGBNOkzOOGNrdrBI=
github.com/infobloxopen/atlas-claims v1.0.0/go.mod h1:6aN87f8OZRqQZ6abcI6tbHiXnE5QiTyNVd31Cb67hy8=
github.com/infobloxopen/atlas-claims v1.1.2 h1:IzKTrRYuXuaBL3gIIffaLT1rOMM7k0ApOPlI6MyP2pE=
github.com/infobloxopen/atlas-claims v1.1.2/go.mod h1:6aN87f8OZRqQZ6abcI6tbHiXnE5QiTyNVd31Cb67hy8=
github.com/infobloxopen/seal v0.2.3 h1:TVIw52FxVVwehat/m23+hjoFXbIvyKBA9XCVI21p68A=
github.com/infobloxopen/seal v0.2.3/go.mod h1:IHbkKw7rx7oJKNtyjHL+1XaGKo5NU8CjFE3ZpA5mrB8=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
Expand Down
65 changes: 39 additions & 26 deletions grpc_opa/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,13 @@ func (a AuthorizeFn) Evaluate(ctx context.Context, fullMethod string, grpcReq in

func NewDefaultAuthorizer(application string, opts ...Option) *DefaultAuthorizer {
cfg := &Config{
address: opa_client.DefaultAddress,
decisionInputHandler: defDecisionInputer,
claimsVerifier: UnverifiedClaimFromBearers,
acctEntitlementsApi: DefaultAcctEntitlementsApiPath,
address: opa_client.DefaultAddress,
decisionInputHandler: defDecisionInputer,
claimsVerifier: UnverifiedClaimFromBearers,
acctEntitlementsApi: DefaultAcctEntitlementsApiPath,
currUserCompartmentsApi: DefaultCurrentUserCompartmentsPath,
filterCompartmentPermsApi: DefaultFilterCompartmentPermissionsApiPath,
filterCompartmentFeatsApi: DefaultFilterCompartmentFeaturesApiPath,
}
for _, opt := range opts {
opt(cfg)
Expand All @@ -137,39 +140,48 @@ func NewDefaultAuthorizer(application string, opts ...Option) *DefaultAuthorizer
}

a := DefaultAuthorizer{
clienter: clienter,
opaEvaluator: cfg.opaEvaluator,
application: application,
decisionInputHandler: cfg.decisionInputHandler,
claimsVerifier: cfg.claimsVerifier,
entitledServices: cfg.entitledServices,
acctEntitlementsApi: cfg.acctEntitlementsApi,
clienter: clienter,
opaEvaluator: cfg.opaEvaluator,
application: application,
decisionInputHandler: cfg.decisionInputHandler,
claimsVerifier: cfg.claimsVerifier,
entitledServices: cfg.entitledServices,
acctEntitlementsApi: cfg.acctEntitlementsApi,
currUserCompartmentsApi: cfg.currUserCompartmentsApi,
filterCompartmentPermsApi: cfg.filterCompartmentPermsApi,
filterCompartmentFeatsApi: cfg.filterCompartmentFeatsApi,
}
return &a
}

type DefaultAuthorizer struct {
application string
clienter opa_client.Clienter
opaEvaluator OpaEvaluator
decisionInputHandler DecisionInputHandler
claimsVerifier ClaimsVerifier
entitledServices []string
acctEntitlementsApi string
application string
clienter opa_client.Clienter
opaEvaluator OpaEvaluator
decisionInputHandler DecisionInputHandler
claimsVerifier ClaimsVerifier
entitledServices []string
acctEntitlementsApi string
currUserCompartmentsApi string
filterCompartmentPermsApi string
filterCompartmentFeatsApi string
}

type Config struct {
httpCli *http.Client
// address to opa
address string

clienter opa_client.Clienter
opaEvaluator OpaEvaluator
authorizer []Authorizer
decisionInputHandler DecisionInputHandler
claimsVerifier ClaimsVerifier
entitledServices []string
acctEntitlementsApi string
clienter opa_client.Clienter
opaEvaluator OpaEvaluator
authorizer []Authorizer
decisionInputHandler DecisionInputHandler
claimsVerifier ClaimsVerifier
entitledServices []string
acctEntitlementsApi string
currUserCompartmentsApi string
filterCompartmentPermsApi string
filterCompartmentFeatsApi string
}

type ClaimsVerifier func([]string, []string) (string, []error)
Expand Down Expand Up @@ -438,6 +450,7 @@ func (o OPAResponse) Obligations() (*ObligationsNode, error) {

func redactJWT(jwt string) string {
parts := strings.Split(jwt, ".")
// Redact signature
if len(parts) > 0 {
parts[len(parts)-1] = REDACTED
}
Expand All @@ -446,7 +459,7 @@ func redactJWT(jwt string) string {

func redactJWTForDebug(jwt string) string {
parts := strings.Split(jwt, ".")
// Redact signature, header and body since we do not want to display any for debug logging
// Redact header/payload/signature, since we do not want to display any for debug logging
for i := range parts {
parts[i] = parts[i][:min(len(parts[i]), 16)] + "/" + REDACTED
}
Expand Down
61 changes: 61 additions & 0 deletions grpc_opa/compartments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package grpc_opa_middleware

import (
"context"
"fmt"

"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
atlas_claims "github.com/infobloxopen/atlas-claims"
logrus "github.com/sirupsen/logrus"
)

const (
// DefaultCurrentUserCompartmentsPath is default OPA path to fetch current user's compartments
DefaultCurrentUserCompartmentsPath = "v1/data/authz/rbac/current_user_compartments"
)

// CurrentUserCompartmentsResult is the data type json.Unmarshaled from OPA RESTAPI query
// to current_user_compartments rego rule
type CurrentUserCompartmentsResult struct {
Result []string `json:"result"`
}

// GetCurrentUserCompartments returns list of compartment-ids
// for the current-user's JWT in the context.
func (a *DefaultAuthorizer) GetCurrentUserCompartments(ctx context.Context) ([]string, error) {
lgNtry := ctxlogrus.Extract(ctx)
cptResult := CurrentUserCompartmentsResult{}

// This fetches auth data from auth headers in metadata from context:
// bearer = data from "authorization bearer" metadata header
// newBearer = data from "set-authorization bearer" metadata header
bearer, newBearer := atlas_claims.AuthBearersFromCtx(ctx)

claimsVerifier := a.claimsVerifier
if claimsVerifier == nil {
claimsVerifier = UnverifiedClaimFromBearers
}

rawJWT, errs := claimsVerifier([]string{bearer}, []string{newBearer})
if len(errs) > 0 {
return nil, fmt.Errorf("%q", errs)
}

opaReq := OPARequest{
Input: &Payload{
JWT: redactJWT(rawJWT),
},
}

err := a.clienter.CustomQuery(ctx, a.currUserCompartmentsApi, opaReq, &cptResult)
if err != nil {
lgNtry.WithError(err).Error("get_curr_user_compartments_fail")
return nil, err
}

lgNtry.WithFields(logrus.Fields{
"cptResult": fmt.Sprintf("%#v", cptResult),
}).Trace("get_curr_user_compartments_okay")

return cptResult.Result, nil
}
193 changes: 193 additions & 0 deletions grpc_opa/compartments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package grpc_opa_middleware

import (
"context"
"io/ioutil"
"reflect"
"sort"
"testing"
"time"

"github.com/infobloxopen/atlas-authz-middleware/pkg/opa_client"
"github.com/infobloxopen/atlas-authz-middleware/utils_test"
atlas_claims "github.com/infobloxopen/atlas-claims"

"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
logrus "github.com/sirupsen/logrus"
)

func TestGetCurrentUserCompartmentsOpa(t *testing.T) {
stdLoggr := logrus.StandardLogger()
ctx, cancel := context.WithCancel(context.Background())
ctx = context.WithValue(ctx, utils_test.TestingTContextKey, t)
ctx = ctxlogrus.ToContext(ctx, logrus.NewEntry(stdLoggr))

done := make(chan struct{})
clienter := utils_test.StartOpa(ctx, t, done)
cli, ok := clienter.(*opa_client.Client)
if !ok {
t.Fatal("Unable to convert interface to (*Client)")
return
}

// Errors above here will leak containers
defer func() {
cancel()
// Wait for container to be shutdown
<-done
}()

policyRego, err := ioutil.ReadFile("testdata/mock_authz_policy.rego")
if err != nil {
t.Fatalf("ReadFile fatal err: %#v", err)
return
}

var resp interface{}
err = cli.UploadRegoPolicy(ctx, "mock_authz_policyid", policyRego, resp)
if err != nil {
t.Fatalf("OpaUploadPolicy fatal err: %#v", err)
return
}

auther := NewDefaultAuthorizer("bogus_unused_application_value",
WithOpaClienter(cli),
)

testCases := []struct {
name string
acctId string
groups []string
expVal []string
}{
{
name: "40; custom-admin-group,user-group-40;",
acctId: "40",
groups: []string{"custom-admin-group", "user-group-40"},
expVal: []string{"compartment-40-red."},
},
{
name: "40; custom-admin-group,user;",
acctId: "40",
groups: []string{"custom-admin-group", "user"},
expVal: []string{"compartment-40-red.", "compartment-40-green."},
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
claims := &atlas_claims.Claims{
AccountId: tt.acctId,
Groups: tt.groups,
}

jwt, err := atlas_claims.BuildJwt(claims, "some-hmac-key-we-dont-care", time.Hour*9)
if err != nil {
t.Fatalf("FAIL: BuildJwt() unexpected err=%v", err)
}

ttCtx := utils_test.ContextWithJWT(ctx, jwt)

gotVal, err := auther.GetCurrentUserCompartments(ttCtx)
if err != nil {
t.Errorf("FAIL: GetCurrentUserCompartments() unexpected err=%v", err)
}

sort.Strings(gotVal)
//t.Logf("gotVal=%#v", gotVal)

sort.Strings(tt.expVal)
if !reflect.DeepEqual(gotVal, tt.expVal) {
t.Errorf("FAIL:\ngotVal: %#v\nexpVal: %#v",
gotVal, tt.expVal)
}
})
}
}

func TestGetCurrentUserCompartmentsMockOpaClient(t *testing.T) {
testCases := []struct {
name string
respJson string
expErr bool
expVal []string
}{
{
name: `valid result`,
respJson: `{ "result": [ "red.", "green.", "blue." ] }`,
expErr: false,
expVal: []string{"red.", "green.", "blue."},
},
{
name: `null result ok`,
respJson: `{ "result": null }`,
expErr: false,
expVal: nil,
},
{
name: `empty result ok`,
respJson: `{ "result": [] }`,
expErr: false,
expVal: []string{},
},
{
name: `incorrect result type`,
respJson: `[ null ]`,
expErr: true,
expVal: nil,
},
{
name: `no result key`,
respJson: `{ "rresult": null }`,
expErr: false,
expVal: nil,
},
{
name: `invalid result object`,
respJson: `{ "result": { "one": 1, "two": 2 } }`,
expErr: true,
expVal: nil,
},
}

stdLoggr := logrus.StandardLogger()
ctx := context.WithValue(context.Background(), utils_test.TestingTContextKey, t)
ctx = ctxlogrus.ToContext(ctx, logrus.NewEntry(stdLoggr))

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
mockOpaClienter := utils_test.MockOpaClienter{
Loggr: stdLoggr,
RegoRespJSON: tt.respJson,
}
auther := NewDefaultAuthorizer("bogus_unused_application_value",
WithOpaClienter(&mockOpaClienter),
)

claims := &atlas_claims.Claims{}
jwt, err := atlas_claims.BuildJwt(claims, "some-hmac-key-we-dont-care", time.Hour*9)
if err != nil {
t.Fatalf("FAIL: BuildJwt() unexpected err=%v", err)
}
ttCtx := utils_test.ContextWithJWT(ctx, jwt)

gotVal, gotErr := auther.GetCurrentUserCompartments(ttCtx)
//t.Logf("gotErr=%#v, gotVal=%#v", gotVal, gotErr)

if tt.expErr && gotErr == nil {
t.Errorf("FAIL: expected err, but got no err")
} else if !tt.expErr && gotErr != nil {
t.Errorf("FAIL: got unexpected err=%s", gotErr)
}

if gotErr != nil && gotVal != nil {
t.Errorf("FAIL: returned val should be nil if err returned")
}

if !reflect.DeepEqual(gotVal, tt.expVal) {
t.Errorf("FAIL: expVal=%#v gotVal=%#v",
tt.expVal, gotVal)
}
})
}
}
Loading
Loading