Skip to content

Commit

Permalink
feat: Rbac cloud (#3203)
Browse files Browse the repository at this point in the history
* chore: mod tidy

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* feat: add cloud rbac support

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: wire up cloud config

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: fix telemetry test

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: add test for fetch

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: update config schema for cloud authz

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: add retryable http client

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: fix cloud issues

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: fix errors

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: fix jwt auth metadata

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

* chore: fix tests

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>

---------

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
  • Loading branch information
markphelps authored Jun 25, 2024
1 parent d8a2b8d commit 6bea103
Show file tree
Hide file tree
Showing 33 changed files with 494 additions and 424 deletions.
8 changes: 4 additions & 4 deletions config/flipt.schema.cue
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ import "strings"

#authorization: {
required?: bool | *false
backend: "local" | "object" | "bundle" | *""
backend: "local" | "object" | "bundle" | "cloud" | *""
local?: {
policy?: {
poll_interval: =~#duration | *"5m"
Expand All @@ -142,6 +142,9 @@ import "strings"
bundle?: {
configuration: string
}
cloud?: {
poll_interval: =~#duration | *"5m"
}
}

#cache: {
Expand Down Expand Up @@ -410,9 +413,6 @@ import "strings"
}

#experimental: {
authorization?: {
enabled?: bool | *false
}
cloud?: {
enabled?: bool | *false
}
Expand Down
29 changes: 16 additions & 13 deletions config/flipt.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@
},
"backend": {
"type": "string",
"enum": ["", "local", "object", "bundle"],
"enum": ["", "local", "object", "bundle", "cloud"],
"default": ""
},
"local": {
Expand All @@ -357,7 +357,8 @@
"path": { "type": "string" },
"poll_interval": {
"type": "string",
"pattern": "^([0-9]+(ns|us|µs|ms|s|m|h))+$"
"pattern": "^([0-9]+(ns|us|µs|ms|s|m|h))+$",
"default": "5m"
}
},
"data": {
Expand All @@ -367,7 +368,8 @@
"path": { "type": "string" },
"poll_interval": {
"type": "string",
"pattern": "^([0-9]+(ns|us|µs|ms|s|m|h))+$"
"pattern": "^([0-9]+(ns|us|µs|ms|s|m|h))+$",
"default": "5m"
}
}
}
Expand Down Expand Up @@ -412,6 +414,17 @@
"type": "string"
}
}
},
"cloud": {
"type": "object",
"additionalProperties": false,
"properties": {
"poll_interval": {
"type": "string",
"pattern": "^([0-9]+(ns|us|µs|ms|s|m|h))+$",
"default": "5m"
}
}
}
}
},
Expand Down Expand Up @@ -1410,16 +1423,6 @@
"default": false
}
}
},
"authorization": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"default": false
}
}
}
},
"title": "Experimental"
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.24.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.54.4
github.com/blang/semver/v4 v4.0.0
github.com/cenkalti/backoff/v4 v4.3.0
github.com/coreos/go-oidc/v3 v3.10.0
github.com/docker/go-connections v0.5.0
github.com/fatih/color v1.17.0
Expand All @@ -47,6 +46,7 @@ require (
github.com/h2non/gock v1.2.0
github.com/hashicorp/cap v0.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/iancoleman/strcase v0.3.0
github.com/jackc/pgx/v5 v5.6.0
Expand Down Expand Up @@ -152,6 +152,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
Expand Down Expand Up @@ -201,7 +202,7 @@ require (
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.6.2 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,12 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I=
github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
Expand Down
2 changes: 2 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g=
github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
Expand Down Expand Up @@ -364,6 +365,7 @@ go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnw
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
Expand Down
19 changes: 16 additions & 3 deletions internal/cmd/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"go.opentelemetry.io/contrib/propagators/autoprop"

sq "github.com/Masterminds/squirrel"
"github.com/hashicorp/go-retryablehttp"
"go.flipt.io/flipt/internal/cache"
"go.flipt.io/flipt/internal/cache/memory"
"go.flipt.io/flipt/internal/cache/redis"
Expand Down Expand Up @@ -376,17 +377,19 @@ func NewGRPCServer(
}

if cfg.Audit.Sinks.Webhook.Enabled {
opts := []webhook.ClientOption{}
httpClient := retryablehttp.NewClient()
httpClient.Logger = logger

if cfg.Audit.Sinks.Webhook.MaxBackoffDuration > 0 {
opts = append(opts, webhook.WithMaxBackoffDuration(cfg.Audit.Sinks.Webhook.MaxBackoffDuration))
httpClient.RetryWaitMax = cfg.Audit.Sinks.Webhook.MaxBackoffDuration
}

var webhookSink audit.Sink

// Enable basic webhook sink if URL is non-empty, otherwise enable template sink if the length of templates is greater
// than 0 for the webhook.
if cfg.Audit.Sinks.Webhook.URL != "" {
webhookSink = webhook.NewSink(logger, webhook.NewWebhookClient(logger, cfg.Audit.Sinks.Webhook.URL, cfg.Audit.Sinks.Webhook.SigningSecret, opts...))
webhookSink = webhook.NewSink(logger, webhook.NewWebhookClient(logger, cfg.Audit.Sinks.Webhook.URL, cfg.Audit.Sinks.Webhook.SigningSecret, httpClient))
} else if len(cfg.Audit.Sinks.Webhook.Templates) > 0 {
maxBackoffDuration := 15 * time.Second
if cfg.Audit.Sinks.Webhook.MaxBackoffDuration > 0 {
Expand Down Expand Up @@ -557,9 +560,19 @@ func getAuthz(ctx context.Context, logger *zap.Logger, cfg *config.Config) (auth
switch cfg.Authorization.Backend {
case config.AuthorizationBackendLocal:
validator, err = authzrego.NewEngine(ctx, logger, cfg)

case config.AuthorizationBackendCloud:
if cfg.Cloud.Authentication.ApiKey == "" {
err = errors.New("cloud authorization requires an api key")
break
}

validator, err = authzrego.NewEngine(ctx, logger, cfg)

default:
validator, err = authzbundle.NewEngine(ctx, logger, cfg)
}

if err != nil {
authzErr = fmt.Errorf("creating authorization policy engine: %w", err)
return
Expand Down
20 changes: 14 additions & 6 deletions internal/config/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,8 @@ func (c *AuditConfig) setDefaults(v *viper.Viper) error {

func (c *AuditConfig) validate() error {
if c.Sinks.Webhook.Enabled {
if c.Sinks.Webhook.URL == "" && len(c.Sinks.Webhook.Templates) == 0 {
return errors.New("url or template(s) not provided")
}

if c.Sinks.Webhook.URL != "" && len(c.Sinks.Webhook.Templates) > 0 {
return errors.New("only one of url or template(s) allowed")
if err := c.Sinks.Webhook.validate(); err != nil {
return err
}
}

Expand Down Expand Up @@ -96,6 +92,18 @@ type WebhookSinkConfig struct {
Templates []WebhookTemplate `json:"templates,omitempty" mapstructure:"templates" yaml:"templates,omitempty"`
}

func (w WebhookSinkConfig) validate() error {
if w.URL == "" && len(w.Templates) == 0 {
return errors.New("url or template(s) not provided")
}

if w.URL != "" && len(w.Templates) > 0 {
return errors.New("only one of url or template(s) allowed")
}

return nil
}

// LogSinkConfig contains fields that hold configuration for sending audits
// to a log.
type LogSinkConfig struct {
Expand Down
59 changes: 45 additions & 14 deletions internal/config/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,36 @@ type AuthorizationConfig struct {
Required bool `json:"required,omitempty" mapstructure:"required" yaml:"required,omitempty"`
Backend AuthorizationBackend `json:"backend,omitempty" mapstructure:"backend" yaml:"backend,omitempty"`
Local *AuthorizationLocalConfig `json:"local,omitempty" mapstructure:"local,omitempty" yaml:"local,omitempty"`
Cloud *AuthorizationSourceCloudConfig `json:"cloud,omitempty" mapstructure:"cloud,omitempty" yaml:"cloud,omitempty"`
Bundle *AuthorizationSourceBundleConfig `json:"bundle,omitempty" mapstructure:"bundle,omitempty" yaml:"bundle,omitempty"`
Object *AuthorizationSourceObjectConfig `json:"object,omitempty" mapstructure:"object,omitempty" yaml:"object,omitempty"`
}

func (c *AuthorizationConfig) setDefaults(v *viper.Viper) error {
auth := map[string]any{"required": false}
if v.GetBool("authorization.required") {
auth["backend"] = AuthorizationBackendLocal
if v.GetString("authorization.local.data.path") == "" {
auth["local"] = map[string]any{
"policy": map[string]any{
"poll_interval": "5m",
},
switch v.GetString("authorization.backend") {
case string(AuthorizationBackendCloud):
auth["cloud"] = map[string]any{
"poll_interval": "5m",
}
} else {
auth["local"] = map[string]any{
"policy": map[string]any{
"poll_interval": "5m",
},
"data": map[string]any{
"poll_interval": "30s",
},
default:
auth["backend"] = AuthorizationBackendLocal
if v.GetString("authorization.local.data.path") == "" {
auth["local"] = map[string]any{
"policy": map[string]any{
"poll_interval": "5m",
},
}
} else {
auth["local"] = map[string]any{
"policy": map[string]any{
"poll_interval": "5m",
},
"data": map[string]any{
"poll_interval": "30s",
},
}
}
}

Expand All @@ -64,6 +72,16 @@ func (c *AuthorizationConfig) validate() error {
if err := c.Local.validate(); err != nil {
return fmt.Errorf("authorization: local: %w", err)
}

case AuthorizationBackendCloud:
if c.Cloud == nil {
return errors.New("authorization: cloud backend must be configured")
}

if err := c.Cloud.validate(); err != nil {
return fmt.Errorf("authorization: cloud: %w", err)
}

case AuthorizationBackendBundle:
if c.Bundle == nil {
return errors.New("authorization: bundle backend must be configured")
Expand Down Expand Up @@ -93,6 +111,7 @@ type AuthorizationBackend string

const (
AuthorizationBackendLocal = AuthorizationBackend("local")
AuthorizationBackendCloud = AuthorizationBackend("cloud")
AuthorizationBackendObject = AuthorizationBackend("object")
AuthorizationBackendBundle = AuthorizationBackend("bundle")
)
Expand Down Expand Up @@ -149,6 +168,18 @@ func (a *AuthorizationSourceLocalConfig) validate() error {
return nil
}

type AuthorizationSourceCloudConfig struct {
PollInterval time.Duration `json:"pollInterval,omitempty" mapstructure:"poll_interval" yaml:"poll_interval,omitempty"`
}

func (a *AuthorizationSourceCloudConfig) validate() error {
if a != nil && a.PollInterval <= 0 {
return errors.New("poll_interval must be greater than zero")
}

return nil
}

type AuthorizationSourceBundleConfig struct {
Configuration string `json:"configuration,omitempty" mapstructure:"configuration" yaml:"configuration,omitempty"`
}
Expand Down
2 changes: 0 additions & 2 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,6 @@ func TestLoad(t *testing.T) {
},
}

cfg.Experimental.Authorization.Enabled = true
return cfg
},
},
Expand Down Expand Up @@ -924,7 +923,6 @@ func TestLoad(t *testing.T) {
},
}

cfg.Experimental.Authorization.Enabled = true
return cfg
},
},
Expand Down
3 changes: 1 addition & 2 deletions internal/config/experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import (
// ExperimentalConfig allows for experimental features to be enabled
// and disabled.
type ExperimentalConfig struct {
Cloud ExperimentalFlag `json:"cloud,omitempty" mapstructure:"cloud" yaml:"cloud,omitempty"`
Authorization ExperimentalFlag `json:"authorization,omitempty" mapstructure:"authorization" yaml:"authorization,omitempty"`
Cloud ExperimentalFlag `json:"cloud,omitempty" mapstructure:"cloud" yaml:"cloud,omitempty"`
}

func (c *ExperimentalConfig) deprecations(v *viper.Viper) []deprecated {
Expand Down
Loading

0 comments on commit 6bea103

Please sign in to comment.