Skip to content

Commit

Permalink
Merge pull request #18 from openfga/adds_mode_support
Browse files Browse the repository at this point in the history
Adds mode support
  • Loading branch information
jcchavezs authored Sep 12, 2024
2 parents 80960c1 + a557d30 commit 181954f
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 65 deletions.
1 change: 1 addition & 0 deletions extauthz/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ build-platform:
.PHONY: docker
docker: build
@docker build --platform=$(DOCKER_PLATFORM) -t gcr.io/openfga/openfga-extauthz:$(PACKAGE_VERSION) -f Dockerfile .
@echo "\nImage available by doing:\n\ndocker pull --platform=$(DOCKER_PLATFORM) gcr.io/openfga/openfga-extauthz:$(PACKAGE_VERSION)\n"

.PHONY: e2e
e2e: e2e-tools
Expand Down
80 changes: 45 additions & 35 deletions extauthz/cmd/extauthz/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,49 +33,59 @@ func main() {
log.Fatalf("failed to load config: %v", err)
}

fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{
ApiUrl: cfg.Server.APIURL,
StoreId: cfg.Server.StoreID,
AuthorizationModelId: cfg.Server.AuthorizationModelID, // optional, recommended to be set for production
})
logger, err := logger.NewLogger(parseLogConfig(cfg.Log)...)
if err != nil {
log.Fatalf("failed to initialize OpenFGA client: %v", err)
log.Fatalf("failed to create logger: %v", err)
}
defer logger.Sync()

extractionSet := make([]extractor.ExtractorKit, 0, len(cfg.ExtractionSet))
for _, es := range cfg.ExtractionSet {
var (
eSet extractor.ExtractorKit
err error
)

eSet.Name = es.Name

eSet.User, err = extractor.MakeExtractor(es.User.Type, es.User.Config)
var filter auth_pb.AuthorizationServer = authz.NoopFilter{}
if cfg.Mode != config.AuthModeDisabled {
fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{
ApiUrl: cfg.Server.APIURL,
StoreId: cfg.Server.StoreID,
AuthorizationModelId: cfg.Server.AuthorizationModelID, // optional, recommended to be set for production
})
if err != nil {
log.Fatalf("failed to create user extractor: %v", err)
logger.Fatal("failed to initialize OpenFGA client", zap.Error(err))
}

eSet.Object, err = extractor.MakeExtractor(es.Object.Type, es.Object.Config)
if err != nil {
log.Fatalf("failed to create object extractor: %v", err)
}
extractionSet := make([]extractor.ExtractorKit, 0, len(cfg.ExtractionSet))
for _, es := range cfg.ExtractionSet {
var (
eSet extractor.ExtractorKit
err error
)

eSet.Relation, err = extractor.MakeExtractor(es.Relation.Type, es.Relation.Config)
if err != nil {
log.Fatalf("failed to create relation extractor: %v", err)
}
eSet.Name = es.Name

extractionSet = append(extractionSet, eSet)
}
eSet.User, err = extractor.MakeExtractor(es.User.Type, es.User.Config)
if err != nil {
logger.Fatal("failed to create user extractor", zap.Error(err))
}

logger, err := logger.NewLogger(parseLogConfig(cfg.Log)...)
if err != nil {
log.Fatalf("failed to create logger: %v", err)
}
defer logger.Sync()
eSet.Object, err = extractor.MakeExtractor(es.Object.Type, es.Object.Config)
if err != nil {
logger.Fatal("failed to create object extractor", zap.Error(err))
}

filter := authz.NewExtAuthZFilter(fgaClient, extractionSet, logger)
eSet.Relation, err = extractor.MakeExtractor(es.Relation.Type, es.Relation.Config)
if err != nil {
logger.Fatal("failed to create relation extractor", zap.Error(err))
}

extractionSet = append(extractionSet, eSet)
}

filter = authz.NewExtAuthZFilter(
authz.Config{
Enforce: cfg.Mode == config.AuthModeEnforce,
ExtractionKits: extractionSet,
},
fgaClient,
logger,
)
}

server := createServer(filter)

Expand All @@ -84,11 +94,11 @@ func main() {
log.Fatalf("failed start listener: %v", err)
}

logger.Info("Starting server", zap.Int("port", port))
logger.Info("Starting server", zap.Int("port", port), zap.String("mode", cfg.Mode.String()))
log.Fatal(server.Serve(listener))
}

func createServer(filter *authz.ExtAuthZFilter) *grpc.Server {
func createServer(filter auth_pb.AuthorizationServer) *grpc.Server {
grpcServer := grpc.NewServer()

grpc_health_v1.RegisterHealthServer(grpcServer, health.NewServer())
Expand Down
5 changes: 4 additions & 1 deletion extauthz/cmd/extauthz/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ func server(ctx context.Context, e extractor.ExtractorKit) (auth_pb.Authorizatio
panic(err)
}

filter := authz.NewExtAuthZFilter(fgaClient, []extractor.ExtractorKit{e}, logger.NewNoopLogger())
filter := authz.NewExtAuthZFilter(authz.Config{
Enforce: true,
ExtractionKits: []extractor.ExtractorKit{e},
}, fgaClient, logger.NewNoopLogger())

baseServer := grpc.NewServer()
auth_pb.RegisterAuthorizationServer(baseServer, filter)
Expand Down
2 changes: 2 additions & 0 deletions extauthz/e2e/config.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ log:
level: debug
format: json

mode: ENFORCE

extraction_sets:
- name: test
user:
Expand Down
6 changes: 3 additions & 3 deletions extauthz/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ module github.com/openfga/openfga-envoy/extauthz
go 1.22.6

require (
github.com/envoyproxy/go-control-plane v0.12.1-0.20240419124334-0cebb2f428b3
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155
github.com/openfga/go-sdk v0.3.5
github.com/openfga/openfga v1.6.0
github.com/openfga/openfga v1.6.1-0.20240906222438-b8787d5f9d21
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd
google.golang.org/grpc v1.65.0
google.golang.org/grpc v1.66.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
12 changes: 6 additions & 6 deletions extauthz/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnTh
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.12.1-0.20240419124334-0cebb2f428b3 h1:/eklMEyfPvB7C8dULCt9GYwpYDy6shwe7vqHMS+82bI=
github.com/envoyproxy/go-control-plane v0.12.1-0.20240419124334-0cebb2f428b3/go.mod h1:rlr50u7tACJ1Y9jCUMndkfLvGCAX3fWXVVAkj+OfzT4=
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc=
github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand All @@ -16,8 +16,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/openfga/go-sdk v0.3.5 h1:KQXhMREh+g/K7HNuZ/YmXuHkREkq0VMKteua4bYr3Uw=
github.com/openfga/go-sdk v0.3.5/go.mod h1:u1iErzj5E9/bhe+8nsMv0gigcYbJtImcdgcE5DmpbBg=
github.com/openfga/openfga v1.6.0 h1:AfTEEK2PJzZPywSYWtOS3IXtHmqPKdLxGr/z+LFUMGk=
github.com/openfga/openfga v1.6.0/go.mod h1:/Q7/Bg/VeN3m68pAcmySejw/Xpp8HwTV92zrsLBoavo=
github.com/openfga/openfga v1.6.1-0.20240906222438-b8787d5f9d21 h1:69fhvi0rg4dGyQ0lcvwVsFtXYvDDkrHPjJEsPFGpgj0=
github.com/openfga/openfga v1.6.1-0.20240906222438-b8787d5f9d21/go.mod h1:UrS0j/EOT9HuQGGIPcyDApOyK6JaOmzqiK1+ex1wKoY=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
Expand All @@ -42,8 +42,8 @@ golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
59 changes: 40 additions & 19 deletions extauthz/internal/server/authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,41 +37,64 @@ var (

// ExtAuthZFilter is an implementation of the Envoy AuthZ filter.
type ExtAuthZFilter struct {
client *client.OpenFgaClient
extractionKit []extractor.ExtractorKit
modelID string
logger logger.Logger
enforce bool
client *client.OpenFgaClient
extractionKits []extractor.ExtractorKit
modelID string
logger logger.Logger
}

var _ envoy.AuthorizationServer = (*ExtAuthZFilter)(nil)

type Config struct {
Enforce bool
ExtractionKits []extractor.ExtractorKit
}

// NewExtAuthZFilter creates a new ExtAuthZFilter
func NewExtAuthZFilter(c *client.OpenFgaClient, es []extractor.ExtractorKit, logger logger.Logger) *ExtAuthZFilter {
return &ExtAuthZFilter{client: c, extractionKit: es, logger: logger}
func NewExtAuthZFilter(config Config, c *client.OpenFgaClient, logger logger.Logger) *ExtAuthZFilter {
return &ExtAuthZFilter{enforce: config.Enforce, client: c, extractionKits: config.ExtractionKits, logger: logger}
}

func (e ExtAuthZFilter) Register(server *grpc.Server) {
envoy.RegisterAuthorizationServer(server, e)
}

// Check the access decision based on the incoming request
func (e ExtAuthZFilter) Check(ctx context.Context, req *envoy.CheckRequest) (response *envoy.CheckResponse, err error) {
res, err := e.check(ctx, req)
if err != nil {
e.logger.Error("failed to check permissions", zap.Error(err))
return nil, err
reqID := req.Attributes.GetRequest().GetHttp().GetHeaders()["x-request-id"]
logger := e.logger
if reqID != "" {
logger = e.logger.With(zap.String("request_id", reqID))
}

return res, nil
res, err := e.check(ctx, req, logger)
if e.enforce {
if err != nil {
logger.Error("Failed to check permissions", zap.Error(err))
return nil, err
}

return res, nil
} else {
if err != nil {
logger.Error("Failed to check permissions", zap.Error(err))
}

return allow, nil
}
}

func (e ExtAuthZFilter) extract(ctx context.Context, req *envoy.CheckRequest) (*extractor.Check, error) {
for _, es := range e.extractionKit {
for _, es := range e.extractionKits {
e.logger.Debug("Extracting values", zap.String("extractor", es.Name))
check, err := es.Extract(ctx, req)
if err == nil {
return check, nil
}

if errors.Is(err, extractor.ErrValueNotFound) {
e.logger.Debug("Extracing value not found", zap.String("extraction_kit", es.Name), zap.Error(err))
continue
}

Expand All @@ -82,16 +105,14 @@ func (e ExtAuthZFilter) extract(ctx context.Context, req *envoy.CheckRequest) (*
}

// Check implements the Check method of the Authorization interface.
func (e ExtAuthZFilter) check(ctx context.Context, req *envoy.CheckRequest) (response *envoy.CheckResponse, err error) {
// TODO: create a new logger when the interface includes the method
reqID := req.Attributes.GetRequest().GetHttp().GetHeaders()["x-request-id"]
func (e ExtAuthZFilter) check(ctx context.Context, req *envoy.CheckRequest, logger logger.Logger) (response *envoy.CheckResponse, err error) {
check, err := e.extract(ctx, req)
if err != nil {
return nil, fmt.Errorf("extracting values from request: %w", err)
}

if check == nil {
e.logger.Error("failed to extract values from request", zap.String("request_id", reqID))
logger.Error("Failed to extract values from request")
return deny(codes.InvalidArgument, "No extraction set found"), nil
}

Expand All @@ -109,15 +130,15 @@ func (e ExtAuthZFilter) check(ctx context.Context, req *envoy.CheckRequest) (res
e.logger.Debug("Checking permissions", zap.String("user", check.User), zap.String("relation", check.Relation), zap.String("object", check.Object))
data, err := e.client.Check(ctx).Body(body).Options(options).Execute()
if err != nil {
e.logger.Error("Failed to check permissions", zap.Error(err), zap.String("request_id", reqID))
logger.Error("Failed to check permissions", zap.Error(err))
return deny(codes.Internal, fmt.Sprintf("Error checking permissions: %v", err)), nil
}

if data.GetAllowed() {
e.logger.Debug("Access granted", zap.String("resolution", data.GetResolution()), zap.String("request_id", reqID))
logger.Debug("Access granted", zap.String("resolution", data.GetResolution()))
return allow, nil
}

e.logger.Debug("Access denied", zap.String("request_id", reqID))
logger.Debug("Access denied")
return deny(codes.PermissionDenied, fmt.Sprintf("Access denied: %s", data.GetResolution())), nil
}
21 changes: 21 additions & 0 deletions extauthz/internal/server/authz/noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package authz

import (
"context"

envoy "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"google.golang.org/grpc"
)

// NoopFilter is a noop implementation of the Envoy AuthZ filter.
type NoopFilter struct{}

var _ envoy.AuthorizationServer = NoopFilter{}

func (e NoopFilter) Register(server *grpc.Server) {
envoy.RegisterAuthorizationServer(server, e)
}

func (e NoopFilter) Check(ctx context.Context, req *envoy.CheckRequest) (response *envoy.CheckResponse, err error) {
return allow, nil
}
40 changes: 39 additions & 1 deletion extauthz/internal/server/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"errors"
"fmt"
"os"

Expand Down Expand Up @@ -52,10 +53,47 @@ type ExtractionSet struct {
Relation Extractor `yaml:"relation"`
}

type AuthMode int8

const (
AuthModeMonitor AuthMode = iota + 1
AuthModeEnforce
AuthModeDisabled
)

func (m AuthMode) String() string {
switch m {
case AuthModeMonitor:
return "MONITOR"
case AuthModeEnforce:
return "ENFORCE"
case AuthModeDisabled:
return "DISABLED"
}

return "UNKNOWN"
}

func (m *AuthMode) UnmarshalYAML(value *yaml.Node) error {
switch value.Value {
case "ENFORCE":
*m = AuthModeEnforce
case "DISABLED":
*m = AuthModeDisabled
case "MONITOR":
*m = AuthModeMonitor
default:
return errors.New("unknown mode")
}

return nil
}

type Config struct {
ExtractionSet []ExtractionSet `yaml:"extraction_sets"`
Server Server `yaml:"server"`
Log Log
Log Log `yaml:"log"`
Mode AuthMode `yaml:"mode"`
}

type Log struct {
Expand Down
2 changes: 2 additions & 0 deletions extauthz/internal/server/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ func TestConfig(t *testing.T) {
require.Equal(t, "text", cfg.Log.Format)
require.Equal(t, "ISO8601", cfg.Log.TimestampFormat)

require.Equal(t, AuthModeEnforce, cfg.Mode)

require.Len(t, cfg.ExtractionSet, 1)
require.Equal(t, "test", cfg.ExtractionSet[0].Name)
require.Equal(t, "mock", cfg.ExtractionSet[0].User.Type)
Expand Down
2 changes: 2 additions & 0 deletions extauthz/internal/server/config/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ log:
format: text
timestamp_format: "ISO8601"

mode: ENFORCE

extraction_sets:
- name: test
user:
Expand Down
Loading

0 comments on commit 181954f

Please sign in to comment.