Skip to content

Commit a524185

Browse files
markphelpsGeorgeMackodiakhq[bot]
authored
fix: check authz for evaluation endpoints (#3438)
* fix: disable authz for all evaluation endpoints Signed-off-by: Mark Phelps <[email protected]> * chore: refactor authz test a bit Signed-off-by: Mark Phelps <[email protected]> * chore: add missing test Signed-off-by: Mark Phelps <[email protected]> * fix(rpc/flipt): implement Requester for evaluation data snapshot (#3439) * chore: fix lint error Signed-off-by: Mark Phelps <[email protected]> * chore: try to fix snapshot IT Signed-off-by: Mark Phelps <[email protected]> --------- Signed-off-by: Mark Phelps <[email protected]> Co-authored-by: George <[email protected]> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent c4a0f96 commit a524185

File tree

12 files changed

+244
-178
lines changed

12 files changed

+244
-178
lines changed

build/testing/integration.go

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ var (
5858
"api/cockroach": withCockroach(api),
5959
"api/cache": cache,
6060
"api/cachetls": cacheWithTLS,
61-
"api/snapshot": snapshot,
62-
"api/ofrep": ofrep,
61+
"api/snapshot": withAuthz(snapshot),
62+
"api/ofrep": withAuthz(ofrep),
6363
"fs/git": git,
6464
"fs/local": local,
6565
"fs/s3": s3,
@@ -671,18 +671,35 @@ func authn(ctx context.Context, _ *dagger.Client, base, flipt *dagger.Container,
671671
return suite(ctx, "authn", base, fliptToTest, conf)
672672
}
673673

674-
func authz(ctx context.Context, _ *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error {
675-
var (
676-
policyPath = "/etc/flipt/authz/policy.rego"
677-
policyData = "/etc/flipt/authz/data.json"
678-
)
674+
func authz(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error {
675+
return withAuthz(func(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error {
679676

680-
// create unique instance for test case
681-
fliptToTest := flipt.
682-
WithEnvVariable("FLIPT_AUTHORIZATION_REQUIRED", "true").
683-
WithEnvVariable("FLIPT_AUTHORIZATION_BACKEND", "local").
684-
WithEnvVariable("FLIPT_AUTHORIZATION_LOCAL_POLICY_PATH", policyPath).
685-
WithNewFile(policyPath, `package flipt.authz.v1
677+
// create unique instance for test case
678+
fliptToTest := flipt.
679+
WithEnvVariable("UNIQUE", uuid.New().String()).
680+
WithExec(nil)
681+
682+
// import state into instance before running test
683+
if err := importInto(ctx, base, flipt, fliptToTest, "--address", conf.address, "--token", bootstrapToken); err != nil {
684+
return func() error { return err }
685+
}
686+
687+
return suite(ctx, "authz", base, fliptToTest, conf)
688+
})(ctx, client, base, flipt, conf)
689+
}
690+
691+
func withAuthz(fn testCaseFn) testCaseFn {
692+
return func(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error {
693+
var (
694+
policyPath = "/etc/flipt/authz/policy.rego"
695+
policyData = "/etc/flipt/authz/data.json"
696+
)
697+
698+
return fn(ctx, client, base, flipt.
699+
WithEnvVariable("FLIPT_AUTHORIZATION_REQUIRED", "true").
700+
WithEnvVariable("FLIPT_AUTHORIZATION_BACKEND", "local").
701+
WithEnvVariable("FLIPT_AUTHORIZATION_LOCAL_POLICY_PATH", policyPath).
702+
WithNewFile(policyPath, `package flipt.authz.v1
686703
687704
import data
688705
import rego.v1
@@ -736,8 +753,8 @@ permit_slice(allowed, _) if {
736753
permit_slice(allowed, requested) if {
737754
allowed[_] = requested
738755
}`).
739-
WithEnvVariable("FLIPT_AUTHORIZATION_LOCAL_DATA_PATH", policyData).
740-
WithNewFile(policyData, `{
756+
WithEnvVariable("FLIPT_AUTHORIZATION_LOCAL_DATA_PATH", policyData).
757+
WithNewFile(policyData, `{
741758
"version": "0.1.0",
742759
"roles": [
743760
{
@@ -822,18 +839,11 @@ permit_slice(allowed, requested) if {
822839
]
823840
}
824841
]
825-
}`).
826-
WithEnvVariable("UNIQUE", uuid.New().String()).
827-
WithExec(nil)
828-
829-
// import state into instance before running test
830-
if err := importInto(ctx, base, flipt, fliptToTest, "--address", conf.address, "--token", bootstrapToken); err != nil {
831-
return func() error { return err }
842+
}`),
843+
conf,
844+
)
832845
}
833-
834-
return suite(ctx, "authz", base, fliptToTest, conf)
835846
}
836-
837847
func withWebhook(fn testCaseFn) testCaseFn {
838848
return func(ctx context.Context, client *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error {
839849
owntracks := client.Container().From("frxyt/gohrec").WithExposedPort(8080).AsService()

build/testing/integration/authz/auth.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go.flipt.io/build/testing/integration"
1212
"go.flipt.io/flipt/rpc/flipt"
1313
"go.flipt.io/flipt/rpc/flipt/auth"
14+
"go.flipt.io/flipt/rpc/flipt/evaluation"
1415
sdk "go.flipt.io/flipt/sdk/go"
1516
"google.golang.org/grpc/codes"
1617
"google.golang.org/grpc/status"
@@ -19,6 +20,30 @@ import (
1920
func Common(t *testing.T, opts integration.TestOpts) {
2021
client := opts.TokenClient(t)
2122

23+
t.Run("Evaluation", func(t *testing.T) {
24+
ctx := context.Background()
25+
26+
t.Run("Boolean", func(t *testing.T) {
27+
_, err := client.Evaluation().Boolean(ctx, &evaluation.EvaluationRequest{
28+
FlagKey: "flag_boolean",
29+
Context: map[string]string{
30+
"in_segment": "segment_001",
31+
},
32+
})
33+
require.NoError(t, err)
34+
})
35+
36+
t.Run("Variant", func(t *testing.T) {
37+
_, err := client.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{
38+
FlagKey: "flag_001",
39+
Context: map[string]string{
40+
"in_segment": "segment_001",
41+
},
42+
})
43+
require.NoError(t, err)
44+
})
45+
})
46+
2247
t.Run("Authentication Methods", func(t *testing.T) {
2348
ctx := context.Background()
2449

@@ -124,19 +149,6 @@ func Common(t *testing.T, opts integration.TestOpts) {
124149
})
125150
}
126151

127-
func listFlagIsAllowed(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) {
128-
t.Helper()
129-
130-
t.Run(fmt.Sprintf("ListFlags(namespace: %q)", namespace), func(t *testing.T) {
131-
// construct a new client using the previously obtained client token
132-
resp, err := client.Flipt().ListFlags(ctx, &flipt.ListFlagRequest{
133-
NamespaceKey: namespace,
134-
})
135-
require.NoError(t, err)
136-
require.NotEmpty(t, resp.Flags)
137-
})
138-
}
139-
140152
func canReadAllIn(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) {
141153
t.Run("CanReadAll", func(t *testing.T) {
142154
clientCallSet{

build/testing/integration/snapshot/snapshot_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
func TestSnapshot(t *testing.T) {
1616
integration.Harness(t, func(t *testing.T, opts integration.TestOpts) {
1717
var (
18-
httpClient = opts.HTTPClient(t)
18+
httpClient = opts.HTTPClient(t, integration.WithRole("viewer")) // TODO: test other roles/namespace combinations
1919
protocol = opts.Protocol()
2020
)
2121

internal/server/authz/middleware/grpc/middleware.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,21 @@ func AuthorizationRequiredInterceptor(logger *zap.Logger, policyVerifier authz.V
9090
return ctx, errUnauthorized
9191
}
9292

93-
request := requester.Request()
94-
95-
allowed, err := policyVerifier.IsAllowed(ctx, map[string]interface{}{
96-
"request": request,
97-
"authentication": auth,
98-
})
99-
100-
if err != nil {
101-
logger.Error("unauthorized", zap.Error(err))
102-
return ctx, errUnauthorized
103-
}
104-
105-
if !allowed {
106-
logger.Error("unauthorized", zap.String("reason", "permission denied"))
107-
return ctx, errUnauthorized
93+
for _, request := range requester.Request() {
94+
allowed, err := policyVerifier.IsAllowed(ctx, map[string]interface{}{
95+
"request": request,
96+
"authentication": auth,
97+
})
98+
99+
if err != nil {
100+
logger.Error("unauthorized", zap.Error(err))
101+
return ctx, errUnauthorized
102+
}
103+
104+
if !allowed {
105+
logger.Error("unauthorized", zap.String("reason", "permission denied"))
106+
return ctx, errUnauthorized
107+
}
108108
}
109109

110110
return handler(ctx, req)

internal/server/evaluation/server_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ func Test_Server_AllowsNamespaceScopedAuthentication(t *testing.T) {
1111
server := &Server{}
1212
assert.True(t, server.AllowsNamespaceScopedAuthentication(context.Background()))
1313
}
14+
15+
func Test_Server_SkipsAuthorization(t *testing.T) {
16+
server := &Server{}
17+
assert.True(t, server.SkipsAuthorization(context.Background()))
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package metadata
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func Test_Server_SkipsAuthorization(t *testing.T) {
11+
server := &Server{}
12+
assert.True(t, server.SkipsAuthorization(context.Background()))
13+
}

internal/server/middleware/grpc/middleware.go

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -239,21 +239,21 @@ func EvaluationUnaryInterceptor(analyticsEnabled bool) grpc.UnaryServerIntercept
239239
// AuditEventUnaryInterceptor captures events and adds them to the trace span to be consumed downstream.
240240
func AuditEventUnaryInterceptor(logger *zap.Logger, eventPairChecker audit.EventPairChecker) grpc.UnaryServerInterceptor {
241241
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
242-
var request flipt.Request
242+
var requests []flipt.Request
243243
r, ok := req.(flipt.Requester)
244244

245245
if !ok {
246246
return handler(ctx, req)
247247
}
248248

249-
request = r.Request()
249+
requests = r.Request()
250250

251-
var event *audit.Event
251+
var events []*audit.Event
252252

253253
actor := authn.ActorFromContext(ctx)
254254

255255
defer func() {
256-
if event != nil {
256+
for _, event := range events {
257257
eventPair := fmt.Sprintf("%s:%s", event.Type, event.Action)
258258

259259
exists := eventPairChecker.Check(eventPair)
@@ -265,51 +265,50 @@ func AuditEventUnaryInterceptor(logger *zap.Logger, eventPairChecker audit.Event
265265
}()
266266

267267
resp, err := handler(ctx, req)
268-
if err != nil {
269-
var uerr errs.ErrUnauthorized
270-
if errors.As(err, &uerr) {
271-
request.Status = flipt.StatusDenied
272-
event = audit.NewEvent(request, actor, nil)
268+
for _, request := range requests {
269+
if err != nil {
270+
var uerr errs.ErrUnauthorized
271+
if errors.As(err, &uerr) {
272+
request.Status = flipt.StatusDenied
273+
events = append(events, audit.NewEvent(request, actor, nil))
274+
}
275+
276+
continue
277+
}
278+
279+
// Delete and Order request(s) have to be handled separately because they do not
280+
// return the concrete type but rather an *empty.Empty response.
281+
if request.Action == flipt.ActionDelete {
282+
events = append(events, audit.NewEvent(request, actor, r))
283+
continue
273284
}
274-
return resp, err
275-
}
276285

277-
// Delete and Order request(s) have to be handled separately because they do not
278-
// return the concrete type but rather an *empty.Empty response.
279-
if request.Action == flipt.ActionDelete {
280-
event = audit.NewEvent(request, actor, r)
281-
} else {
282286
switch r := req.(type) {
283287
case *flipt.OrderRulesRequest, *flipt.OrderRolloutsRequest:
284-
event = audit.NewEvent(request, actor, r)
288+
events = append(events, audit.NewEvent(request, actor, r))
289+
continue
285290
}
286-
}
287291

288-
// Short circuiting the middleware here since we have a non-nil event from
289-
// detecting a delete.
290-
if event != nil {
291-
return resp, err
292-
}
293-
294-
switch r := resp.(type) {
295-
case *flipt.Flag:
296-
event = audit.NewEvent(request, actor, audit.NewFlag(r))
297-
case *flipt.Variant:
298-
event = audit.NewEvent(request, actor, audit.NewVariant(r))
299-
case *flipt.Segment:
300-
event = audit.NewEvent(request, actor, audit.NewSegment(r))
301-
case *flipt.Distribution:
302-
event = audit.NewEvent(request, actor, audit.NewDistribution(r))
303-
case *flipt.Constraint:
304-
event = audit.NewEvent(request, actor, audit.NewConstraint(r))
305-
case *flipt.Namespace:
306-
event = audit.NewEvent(request, actor, audit.NewNamespace(r))
307-
case *flipt.Rollout:
308-
event = audit.NewEvent(request, actor, audit.NewRollout(r))
309-
case *flipt.Rule:
310-
event = audit.NewEvent(request, actor, audit.NewRule(r))
311-
case *auth.CreateTokenResponse:
312-
event = audit.NewEvent(request, actor, r.Authentication.Metadata)
292+
switch r := resp.(type) {
293+
case *flipt.Flag:
294+
events = append(events, audit.NewEvent(request, actor, audit.NewFlag(r)))
295+
case *flipt.Variant:
296+
events = append(events, audit.NewEvent(request, actor, audit.NewVariant(r)))
297+
case *flipt.Segment:
298+
events = append(events, audit.NewEvent(request, actor, audit.NewSegment(r)))
299+
case *flipt.Distribution:
300+
events = append(events, audit.NewEvent(request, actor, audit.NewDistribution(r)))
301+
case *flipt.Constraint:
302+
events = append(events, audit.NewEvent(request, actor, audit.NewConstraint(r)))
303+
case *flipt.Namespace:
304+
events = append(events, audit.NewEvent(request, actor, audit.NewNamespace(r)))
305+
case *flipt.Rollout:
306+
events = append(events, audit.NewEvent(request, actor, audit.NewRollout(r)))
307+
case *flipt.Rule:
308+
events = append(events, audit.NewEvent(request, actor, audit.NewRule(r)))
309+
case *auth.CreateTokenResponse:
310+
events = append(events, audit.NewEvent(request, actor, r.Authentication.Metadata))
311+
}
313312
}
314313

315314
return resp, err

internal/server/ofrep/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ func New(logger *zap.Logger, cacheCfg config.CacheConfig, bridge Bridge) *Server
5656
func (s *Server) RegisterGRPC(server *grpc.Server) {
5757
ofrep.RegisterOFREPServiceServer(server, s)
5858
}
59+
60+
func (s *Server) SkipsAuthorization(ctx context.Context) bool {
61+
return true
62+
}

internal/server/ofrep/server_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ofrep
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func Test_Server_SkipsAuthorization(t *testing.T) {
11+
server := &Server{}
12+
assert.True(t, server.SkipsAuthorization(context.Background()))
13+
}

rpc/flipt/auth/request.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ import (
44
"go.flipt.io/flipt/rpc/flipt"
55
)
66

7-
func (req *CreateTokenRequest) Request() flipt.Request {
8-
return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionCreate, flipt.WithSubject(flipt.SubjectToken))
7+
func (req *CreateTokenRequest) Request() []flipt.Request {
8+
return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionCreate, flipt.WithSubject(flipt.SubjectToken))}
99
}
1010

11-
func (req *ListAuthenticationsRequest) Request() flipt.Request {
12-
return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead)
11+
func (req *ListAuthenticationsRequest) Request() []flipt.Request {
12+
return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead)}
1313
}
1414

15-
func (req *GetAuthenticationRequest) Request() flipt.Request {
16-
return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead)
15+
func (req *GetAuthenticationRequest) Request() []flipt.Request {
16+
return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionRead)}
1717
}
1818

19-
func (req *DeleteAuthenticationRequest) Request() flipt.Request {
20-
return flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionDelete)
19+
func (req *DeleteAuthenticationRequest) Request() []flipt.Request {
20+
return []flipt.Request{flipt.NewRequest(flipt.ResourceAuthentication, flipt.ActionDelete)}
2121
}

0 commit comments

Comments
 (0)