Skip to content

Commit

Permalink
wip: oas middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie authored Jul 18, 2024
1 parent 6b9c065 commit fccc156
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 1 deletion.
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Air",
"type": "go",
"mode": "remote",
"request": "attach",
"host": "127.0.0.1",
"apiVersion": 2,
"trace": "verbose",
"port": 2345
}
]
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
}
}
33 changes: 33 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"version": "2.0.0",
"options": {
"env": {}
},
"tasks": [
{
"label": "Start Web Server",
"type": "shell",
"command": "air",
"options": {
"cwd": "${workspaceFolder}"
},
"args": [
"-c",
"${workspaceFolder}/cmd/web/.air.toml"
],
"problemMatcher": []
},
{
"label": "Start API Server",
"type": "shell",
"command": "air",
"options": {
"cwd": "${workspaceFolder}"
},
"args": [
"-c",
"${workspaceFolder}/cmd/api/.air.toml"
]
}
]
}
13 changes: 12 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
openapi "github.com/zeiss/knox/pkg/apis"
"github.com/zeiss/knox/pkg/auth"
"github.com/zeiss/knox/pkg/cfg"
"github.com/zeiss/knox/pkg/oas"
"github.com/zeiss/knox/pkg/utils"

"github.com/gofiber/fiber/v2"
Expand Down Expand Up @@ -113,8 +114,18 @@ func (s *WebSrv) Start(ctx context.Context, ready server.ReadyFunc, run server.R
pc := controllers.NewProjectController(store)
ec := controllers.NewEnvironmentController(store)

authz := oas.NewAuthz(
oas.Config{
Resolvers: map[string]oas.AuthzResolverFunc{
"GetTeam": func(ctx *fiber.Ctx) (oas.User, oas.Relation, oas.Object, error) {
return oas.User(""), oas.Relation(""), oas.Object(""), nil
},
},
},
)

handlers := handlers.NewAPIHandlers(lc, sc, ssc, tc, pc, ec)
handler := openapi.NewStrictHandler(handlers, nil)
handler := openapi.NewStrictHandler(handlers, []openapi.StrictMiddlewareFunc{authz})
openapi.RegisterHandlers(app, handler)

err = app.Listen(s.cfg.Flags.Addr)
Expand Down
172 changes: 172 additions & 0 deletions pkg/oas/oas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package oas

import (
"context"

openapi "github.com/zeiss/knox/pkg/apis"

"github.com/gofiber/fiber/v2"
"github.com/openfga/go-sdk/client"
)

// User is the subject.
type User string

// String returns the string representation of the user.
func (u User) String() string {
return string(u)
}

// Relation is the object.
type Relation string

// String returns the string representation of the relation.
func (r Relation) String() string {
return string(r)
}

// Object is the action.
type Object string

// String returns the string representation of the object.
func (o Object) String() string {
return string(o)
}

const (
NoopUser User = ""
NoopRelation Relation = ""
NoopObject Object = ""
)

// Checker is the interface for the FGA authz checker.
type Checker interface {
Allowed(ctx context.Context, user User, relation Relation, object Object) (bool, error)
}

var _ Checker = (*fgaImpl)(nil)

type fgaImpl struct {
client *client.OpenFgaClient
}

// NewChecker returns a new FGA authz checker.
func NewChecker(c *client.OpenFgaClient) *fgaImpl {
return &fgaImpl{c}
}

// Allowed returns true if user has the relation with the object.
func (f *fgaImpl) Allowed(ctx context.Context, user User, relation Relation, object Object) (bool, error) {
body := client.ClientCheckRequest{
User: user.String(),
Relation: relation.String(),
Object: object.String(),
}

allowed, err := f.client.Check(ctx).Body(body).Execute()
if err != nil {
return false, err
}

return allowed.GetAllowed(), nil
}

type noopImpl struct{}

// NewNoop returns a new Noop authz checker.
func NewNoop() *noopImpl {
return &noopImpl{}
}

// Allowed returns true if user has the relation with the object.
func (n *noopImpl) Allowed(ctx context.Context, user User, relation Relation, object Object) (bool, error) {
return false, nil
}

// NoopResolvers is a map of Noop resolvers.
func NoopResolvers() map[string]AuthzResolverFunc {
return map[string]AuthzResolverFunc{}
}

// Config ...
type Config struct {
// Next defines a function to skip the current middleware.
Next func(c *fiber.Ctx) bool
// Checker defines a function to check the authorization.
Checker Checker
// Resolvers defines the resolvers for a specific operation.
Resolvers map[string]AuthzResolverFunc
// DefaultError defines the default error.
DefaultError error
}

// DefaultConfig contains the default configuration.
var DefaultConfig = Config{
Checker: NewNoop(),
Resolvers: NoopResolvers(),
DefaultError: fiber.ErrForbidden,
}

// NoopResolver is a resolver that always returns Noop values.
func NoopResolver() AuthzResolverFunc {
return func(ctx *fiber.Ctx) (User, Relation, Object, error) {
return NoopUser, NoopRelation, NoopObject, nil
}
}

// AuthzResolverFunc is a function to resolve the authz values.
type AuthzResolverFunc func(ctx *fiber.Ctx) (User, Relation, Object, error)

// NewAuthz returns a new authz middleware.
func NewAuthz(config ...Config) openapi.StrictMiddlewareFunc {
cfg := configDefault(config...)

return func(f openapi.StrictHandlerFunc, operationID string) openapi.StrictHandlerFunc {
return func(ctx *fiber.Ctx, args interface{}) (interface{}, error) {
if cfg.Next != nil && cfg.Next(ctx) {
return f(ctx, args)
}

resolver, ok := cfg.Resolvers[operationID]
if !ok {
return nil, cfg.DefaultError
}

user, relation, object, err := resolver(ctx)
if err != nil {
return nil, cfg.DefaultError
}

allowed, err := cfg.Checker.Allowed(ctx.Context(), user, relation, object)
if err != nil {
return nil, cfg.DefaultError
}

if !allowed {
return nil, cfg.DefaultError
}

return f(ctx, args)
}
}
}

// Helper function to set default values
func configDefault(config ...Config) Config {
if len(config) < 1 {
return DefaultConfig
}

// Override default config
cfg := config[0]

if cfg.Checker == nil {
cfg.Checker = DefaultConfig.Checker
}

if cfg.DefaultError == nil {
cfg.DefaultError = DefaultConfig.DefaultError
}

return cfg
}

0 comments on commit fccc156

Please sign in to comment.