Skip to content

Commit

Permalink
Merge branch 'authz' of https://github.com/flipt-io/flipt into authz
Browse files Browse the repository at this point in the history
* 'authz' of https://github.com/flipt-io/flipt:
  chore: set raw claims if they exist in authz metadata (#3125)
  • Loading branch information
markphelps committed May 28, 2024
2 parents b0b2114 + b15e83e commit c39af9a
Show file tree
Hide file tree
Showing 14 changed files with 58 additions and 206 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/iancoleman/strcase v0.3.0
github.com/jackc/pgx/v5 v5.5.5
github.com/jmespath/go-jmespath v0.4.0
github.com/libsql/libsql-client-go v0.0.0-20230917132930-48c310b27e7b
github.com/magefile/mage v1.15.0
github.com/mattn/go-sqlite3 v1.14.22
Expand Down Expand Up @@ -205,6 +204,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3r
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ func NewGRPCServer(

interceptors = append(interceptors, authzmiddlewaregrpc.AuthorizationRequiredInterceptor(logger, policyEngine, authzOpts...))

logger.Debug("authorization required")
logger.Info("authorization middleware enabled")
}

// audit sinks configuration
Expand Down
13 changes: 6 additions & 7 deletions internal/config/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,13 +469,12 @@ func (a AuthenticationMethodOIDCConfig) validate() error {

// AuthenticationOIDCProvider configures provider credentials
type AuthenticationMethodOIDCProvider struct {
IssuerURL string `json:"issuerURL,omitempty" mapstructure:"issuer_url" yaml:"issuer_url,omitempty"`
ClientID string `json:"-,omitempty" mapstructure:"client_id" yaml:"-"`
ClientSecret string `json:"-" mapstructure:"client_secret" yaml:"-"`
RedirectAddress string `json:"redirectAddress,omitempty" mapstructure:"redirect_address" yaml:"redirect_address,omitempty"`
Scopes []string `json:"scopes,omitempty" mapstructure:"scopes" yaml:"scopes,omitempty"`
UsePKCE bool `json:"usePKCE,omitempty" mapstructure:"use_pkce" yaml:"use_pkce,omitempty"`
RoleAttributePath string `json:"roleAttributePath,omitempty" mapstructure:"role_attribute_path" yaml:"role_attribute_path,omitempty"`
IssuerURL string `json:"issuerURL,omitempty" mapstructure:"issuer_url" yaml:"issuer_url,omitempty"`
ClientID string `json:"-,omitempty" mapstructure:"client_id" yaml:"-"`
ClientSecret string `json:"-" mapstructure:"client_secret" yaml:"-"`
RedirectAddress string `json:"redirectAddress,omitempty" mapstructure:"redirect_address" yaml:"redirect_address,omitempty"`
Scopes []string `json:"scopes,omitempty" mapstructure:"scopes" yaml:"scopes,omitempty"`
UsePKCE bool `json:"usePKCE,omitempty" mapstructure:"use_pkce" yaml:"use_pkce,omitempty"`
}

func (a AuthenticationMethodOIDCProvider) validate() error {
Expand Down
6 changes: 3 additions & 3 deletions internal/server/authn/method/metadata.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package method

const (
StorageMetadataRole = "io.flipt.auth.role"
StorageMetadataEmail = "io.flipt.auth.email"
StorageMetadataName = "io.flipt.auth.name"
StorageMetadataClaims = "io.flipt.auth.claims"
StorageMetadataEmail = "io.flipt.auth.email"
StorageMetadataName = "io.flipt.auth.name"
)
23 changes: 10 additions & 13 deletions internal/server/authn/method/oidc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package oidc

import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -145,23 +146,19 @@ func (s *Server) Callback(ctx context.Context, req *auth.CallbackRequest) (_ *au
storageMetadataOIDCProvider: req.Provider,
}

cfg, err := s.configFor(req.Provider)
if err != nil {
rawClaims := make(map[string]interface{})
if err := responseToken.IDToken().Claims(&rawClaims); err != nil {
return nil, err
}

if cfg.RoleAttributePath != "" {
rawClaims := make(map[string]interface{})
if err := responseToken.IDToken().Claims(&rawClaims); err != nil {
return nil, err
}

role, err := method.SearchJSONForAttr[string](cfg.RoleAttributePath, rawClaims)
if err != nil {
return nil, err
}
// marshal raw claims to JSON
rawClaimsJSON, err := json.Marshal(rawClaims)
if err != nil {
return nil, err
}

metadata[method.StorageMetadataRole] = role
if rawClaimsJSON != nil {
metadata[method.StorageMetadataClaims] = string(rawClaimsJSON)
}

// Extract custom claims
Expand Down
30 changes: 16 additions & 14 deletions internal/server/authn/method/oidc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,10 @@ func Test_Server_ImplicitFlow(t *testing.T) {
Method: config.AuthenticationMethodOIDCConfig{
Providers: map[string]config.AuthenticationMethodOIDCProvider{
"google": {
IssuerURL: tp.Addr(),
ClientID: id,
ClientSecret: secret,
RedirectAddress: clientAddress,
RoleAttributePath: "contains(roles[*], 'admin') && 'admin' || contains(roles[*], 'editor') && 'editor' || 'viewer'",
IssuerURL: tp.Addr(),
ClientID: id,
ClientSecret: secret,
RedirectAddress: clientAddress,
},
},
},
Expand Down Expand Up @@ -215,12 +214,11 @@ func Test_Server_PKCE(t *testing.T) {
Method: config.AuthenticationMethodOIDCConfig{
Providers: map[string]config.AuthenticationMethodOIDCProvider{
"google": {
IssuerURL: tp.Addr(),
ClientID: id,
ClientSecret: secret,
RedirectAddress: clientAddress,
UsePKCE: true,
RoleAttributePath: "contains(roles[*], 'admin') && 'admin' || contains(roles[*], 'editor') && 'editor' || 'viewer'",
IssuerURL: tp.Addr(),
ClientID: id,
ClientSecret: secret,
RedirectAddress: clientAddress,
UsePKCE: true,
},
},
},
Expand Down Expand Up @@ -346,15 +344,19 @@ func testOIDCFlow(t *testing.T, ctx context.Context, tpAddr, clientAddress strin

assert.Empty(t, response.ClientToken) // middleware moves it to cookie
assert.Equal(t, auth.Method_METHOD_OIDC, response.Authentication.Method)
assert.Equal(t, map[string]string{

for k, v := range map[string]string{
"io.flipt.auth.oidc.provider": "google",
"io.flipt.auth.oidc.email": "[email protected]",
"io.flipt.auth.email": "[email protected]",
"io.flipt.auth.oidc.name": "Mark Phelps",
"io.flipt.auth.name": "Mark Phelps",
"io.flipt.auth.oidc.sub": "mark",
"io.flipt.auth.role": "admin",
}, response.Authentication.Metadata)
} {
assert.Equal(t, v, response.Authentication.Metadata[k])
}

assert.NotEmpty(t, response.Authentication.Metadata["io.flipt.auth.claims"])

// ensure expiry is set
assert.NotNil(t, response.Authentication.ExpiresAt)
Expand Down
47 changes: 0 additions & 47 deletions internal/server/authn/method/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ package method
import (
"context"

"encoding/json"
"fmt"

"github.com/jmespath/go-jmespath"
"go.flipt.io/flipt/errors"
"google.golang.org/grpc/metadata"
)
Expand All @@ -30,46 +26,3 @@ func CallbackValidateState(ctx context.Context, state string) error {

return nil
}

func SearchJSONForAttr[T ~string](attributePath string, data any) (T, error) {
v, err := searchJSONForAttr(attributePath, data)
if err != nil {
return "", err
}
return v.(T), nil
}

func searchJSONForAttr(attributePath string, data any) (any, error) {
if attributePath == "" {
return "", fmt.Errorf("attribute path: %q", attributePath)
}

if data == nil {
return "", fmt.Errorf("empty json, attribute path: %q", attributePath)
}

// Copy the data to a new variable
var jsonData = data

// If the data is a byte slice, try to unmarshal it into a JSON object
if dataBytes, ok := data.([]byte); ok {
// If the byte slice is empty, return an error
if len(dataBytes) == 0 {
return "", fmt.Errorf("empty json, attribute path: %q", attributePath)
}

// Try to unmarshal the byte slice
if err := json.Unmarshal(dataBytes, &jsonData); err != nil {
return "", fmt.Errorf("%v: %w", "failed to unmarshal user info JSON response", err)
}
}

// Search for the attribute in the JSON object
value, err := jmespath.Search(attributePath, jsonData)
if err != nil {
return "", fmt.Errorf("failed to search user info JSON response with provided path: %q: %w", attributePath, err)
}

// Return the value and nil error
return value, nil
}
4 changes: 4 additions & 0 deletions internal/server/authn/public/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ func (s *Server) RegisterGRPC(server *grpc.Server) {
func (s *Server) SkipsAuthentication(ctx context.Context) bool {
return true
}

func (s *Server) SkipsAuthorization(ctx context.Context) bool {
return true
}
2 changes: 2 additions & 0 deletions internal/server/authz/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ func (e *Engine) IsAllowed(ctx context.Context, input map[string]interface{}) (b
e.mu.RLock()
defer e.mu.RUnlock()

e.logger.Debug("evaluating policy", zap.Any("input", input))

results, err := e.query.Eval(ctx, rego.EvalInput(input))
if err != nil {
return false, err
Expand Down
14 changes: 14 additions & 0 deletions internal/server/authz/middleware/grpc/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ func AuthorizationRequiredInterceptor(logger *zap.Logger, policyVerifier authz.V
return ctx, errUnauthorized
}

// unmarshal auth.metadata["io.flipt.auth.claims"] if present to make writing policies easier
// if auth.Metadata != nil {
// if claims, ok := auth.Metadata["io.flipt.auth.claims"]; ok {
// var claimsMap map[string]interface{}

// if err := json.Unmarshal([]byte(claims), &claimsMap); err != nil {
// logger.Error("unauthorized", zap.String("reason", "failed to unmarshal claims"))
// return ctx, errUnauthorized
// }

// auth.Metadata["io.flipt.auth.claims"] = claimsMap
// }
// }

allowed, err := policyVerifier.IsAllowed(ctx, map[string]interface{}{
"request": requester.Request(),
"authentication": auth,
Expand Down
26 changes: 0 additions & 26 deletions internal/server/authz/policies/default.json

This file was deleted.

68 changes: 0 additions & 68 deletions internal/server/authz/policies/policy.schema.json

This file was deleted.

26 changes: 0 additions & 26 deletions internal/server/authz/policies/schema_test.go

This file was deleted.

0 comments on commit c39af9a

Please sign in to comment.