diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 9aec9395c8..3e7c448564 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -66,6 +66,7 @@ jobs: "fs/gcs", "import/export", "authn/sqlite", + "authz/sqlite", ] steps: - uses: actions/checkout@v4 diff --git a/build/testing/integration.go b/build/testing/integration.go index f142e26452..0d886523bc 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -66,6 +66,7 @@ var ( "fs/gcs": gcs, "import/export": importExport, "authn/sqlite": authn, + "authz/sqlite": authz, } ) @@ -139,24 +140,25 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger flipt = flipt. WithEnvVariable("FLIPT_AUTHENTICATION_REQUIRED", "true"). WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_ENABLED", "true"). - WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken) + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_TOKEN", bootstrapToken). + WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_TOKEN_BOOTSTRAP_METADATA_IS_BOOTSTRAP", "true") } { // K8s auth configuration flipt = flipt. WithEnvVariable("FLIPT_AUTHENTICATION_METHODS_KUBERNETES_ENABLED", "true") - var saToken string - // run an OIDC server which exposes a JWKS url using a private key we own - // and generate a JWT to act as our SA token - flipt, saToken, err = serveOIDC(ctx, client, base, flipt) + var priv []byte + // run an OIDC server which exposes a JWKS url and returns + // the associated private key bytes + flipt, priv, err = serveOIDC(ctx, client, base, flipt) if err != nil { return err } // mount service account token into base on expected k8s sa token path - base = base.WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/token", dagger.ContainerWithNewFileOpts{ - Contents: saToken, + base = base.WithNewFile("/var/run/secrets/flipt/k8s.pem", dagger.ContainerWithNewFileOpts{ + Contents: string(priv), }) } { @@ -182,7 +184,7 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger Bytes: x509.MarshalPKCS1PrivateKey(priv), }) - base = base.WithNewFile("/var/run/secrets/flipt/private.pem", dagger.ContainerWithNewFileOpts{ + base = base.WithNewFile("/var/run/secrets/flipt/jwt.pem", dagger.ContainerWithNewFileOpts{ Contents: string(privBytes), }) } @@ -202,9 +204,11 @@ func Integration(ctx context.Context, client *dagger.Client, base, flipt *dagger err = g.Wait() if _, lerr := client.Container().From("alpine:3.16"). + WithEnvVariable("UNIQUE", uuid.New().String()). WithMountedCache("/logs", logs). WithExec([]string{"cp", "-r", "/logs", "/out"}). - Directory("/out").Export(ctx, "build/logs"); lerr != nil { + Directory("/out"). + Export(ctx, "build/logs"); lerr != nil { log.Println("Error copying logs", lerr) } @@ -642,6 +646,173 @@ func authn(ctx context.Context, _ *dagger.Client, base, flipt *dagger.Container, return suite(ctx, "authn", base, fliptToTest, conf) } +func authz(ctx context.Context, _ *dagger.Client, base, flipt *dagger.Container, conf testConfig) func() error { + var ( + policyPath = "/etc/flipt/authz/policy.rego" + policyData = "/etc/flipt/authz/data.json" + ) + + // create unique instance for test case + fliptToTest := flipt. + WithEnvVariable("FLIPT_EXPERIMENTAL_AUTHORIZATION_ENABLED", "true"). + WithEnvVariable("FLIPT_AUTHORIZATION_REQUIRED", "true"). + WithEnvVariable("FLIPT_AUTHORIZATION_POLICY_LOCAL_PATH", policyPath). + WithNewFile(policyPath, dagger.ContainerWithNewFileOpts{ + Contents: `package flipt.authz.v1 + +import data +import rego.v1 + +default allow = false + +allow if { + input.authentication.metadata["is_bootstrap"] == "true" +} + +allow if { + some rule in has_rules + + permit_string(rule.resource, input.request.resource) + permit_slice(rule.actions, input.request.action) + permit_string(rule.namespace, input.request.namespace) +} + +allow if { + some rule in has_rules + + permit_string(rule.resource, input.request.resource) + permit_slice(rule.actions, input.request.action) + not rule.namespace +} + +has_rules contains rules if { + some role in data.roles + role.name == input.authentication.metadata["io.flipt.auth.role"] + rules := role.rules[_] +} + +has_rules contains rules if { + some role in data.roles + role.name == input.authentication.metadata["io.flipt.auth.k8s.serviceaccount.name"] + rules := role.rules[_] +} + +permit_string(allowed, _) if { + allowed == "*" +} + +permit_string(allowed, requested) if { + allowed == requested +} + +permit_slice(allowed, _) if { + allowed[_] = "*" +} + +permit_slice(allowed, requested) if { + allowed[_] = requested +}`, + }). + WithEnvVariable("FLIPT_AUTHORIZATION_DATA_LOCAL_PATH", policyData). + WithNewFile(policyData, dagger.ContainerWithNewFileOpts{ + Contents: `{ + "version": "0.1.0", + "roles": [ + { + "name": "admin", + "rules": [ + { + "resource": "*", + "actions": [ + "*" + ] + } + ] + }, + { + "name": "editor", + "rules": [ + { + "resource": "namespace", + "actions": [ + "read" + ] + }, + { + "resource": "authentication", + "actions": [ + "read" + ] + }, + { + "resource": "flag", + "actions": [ + "create", + "read", + "update", + "delete" + ] + }, + { + "resource": "segment", + "actions": [ + "create", + "read", + "update", + "delete" + ] + } + ] + }, + { + "name": "viewer", + "rules": [ + { + "resource": "*", + "actions": [ + "read" + ] + } + ] + }, + { + "name": "default_viewer", + "rules": [ + { + "resource": "*", + "actions": [ + "read" + ], + "namespace": "default" + } + ] + }, + { + "name": "production_viewer", + "rules": [ + { + "resource": "*", + "actions": [ + "read" + ], + "namespace": "production" + } + ] + } + ] +}`, + }). + WithEnvVariable("UNIQUE", uuid.New().String()). + WithExec(nil) + + // import state into instance before running test + if err := importInto(ctx, base, flipt, fliptToTest, "--address", conf.address, "--token", bootstrapToken); err != nil { + return func() error { return err } + } + + return suite(ctx, "authz", base, fliptToTest, conf) +} + func suite(ctx context.Context, dir string, base, flipt *dagger.Container, conf testConfig) func() error { return func() (err error) { flags := []string{"--flipt-addr", conf.address, "--flipt-token", bootstrapToken} @@ -746,10 +917,10 @@ func signJWT(key crypto.PrivateKey, claims interface{}) string { // The function generates two JWTs, one for Flipt to identify itself and one which is returned to the caller. // The caller can use this as the service account token identity to be mounted into the container with the // client used for running the test and authenticating with Flipt. -func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Container) (*dagger.Container, string, error) { +func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Container) (*dagger.Container, []byte, error) { priv, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { - return nil, "", err + return nil, nil, err } rsaSigningKey := &bytes.Buffer{} @@ -757,7 +928,7 @@ func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Containe Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv), }); err != nil { - return nil, "", err + return nil, nil, err } // generate a SA style JWT for identifying the Flipt service @@ -799,12 +970,12 @@ func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Containe caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { - return nil, "", err + return nil, nil, err } caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) if err != nil { - return nil, "", err + return nil, nil, err } var caCert bytes.Buffer @@ -812,7 +983,7 @@ func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Containe Type: "CERTIFICATE", Bytes: caBytes, }); err != nil { - return nil, "", err + return nil, nil, err } var caPrivKeyPEM bytes.Buffer @@ -845,21 +1016,5 @@ func serveOIDC(_ context.Context, _ *dagger.Client, base, flipt *dagger.Containe dagger.ContainerWithNewFileOpts{Contents: fliptSAToken}). WithNewFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", dagger.ContainerWithNewFileOpts{Contents: caCert.String()}), - // generate a JWT to used to identify the workload communicating with Flipt - // using the private key components of the key pair served by the OIDC server - signJWT(priv, map[string]any{ - "exp": time.Now().Add(24 * time.Hour).Unix(), - "iss": "https://discover.svc", - "kubernetes.io": map[string]any{ - "namespace": "integration", - "pod": map[string]any{ - "name": "integration-test-7d26f049-kdurb", - "uid": "bd8299f9-c50f-4b76-af33-9d8e3ef2b850", - }, - "serviceaccount": map[string]any{ - "name": "integration-test", - "uid": "4f18914e-f276-44b2-aebd-27db1d8f8def", - }, - }, - }), nil + rsaSigningKey.Bytes(), nil } diff --git a/build/testing/integration/api/api.go b/build/testing/integration/api/api.go index 5932c4df0b..75dc6ee875 100644 --- a/build/testing/integration/api/api.go +++ b/build/testing/integration/api/api.go @@ -21,7 +21,7 @@ import ( func API(t *testing.T, ctx context.Context, opts integration.TestOpts) { var ( - client = opts.DefaultClient(t) + client = opts.TokenClient(t) protocol = opts.Protocol() ) diff --git a/build/testing/integration/authn/auth.go b/build/testing/integration/authn/auth.go index 545e0f20af..2a1fd96436 100644 --- a/build/testing/integration/authn/auth.go +++ b/build/testing/integration/authn/auth.go @@ -16,7 +16,7 @@ import ( ) func Common(t *testing.T, opts integration.TestOpts) { - client := opts.DefaultClient(t) + client := opts.TokenClient(t) t.Run("Authentication Methods", func(t *testing.T) { ctx := context.Background() @@ -47,9 +47,6 @@ func Common(t *testing.T, opts integration.TestOpts) { }) t.Run("StaticToken", func(t *testing.T) { - // copy options so we can make some more clients with the generated tokens - opts := opts - // ensure we can do resource specific operations across namespaces canReadAllIn(t, ctx, client, namespace.Key) @@ -73,18 +70,18 @@ func Common(t *testing.T, opts integration.TestOpts) { } else { assert.NotContains(t, resp.Authentication.Metadata, "io.flipt.auth.token.namespace") } - - opts.Token = resp.ClientToken }) // ensure we can do resource specific operations in scoped namespace - canReadAllIn(t, ctx, opts.DefaultClient(t), namespace.Key) + scopedClient := opts.TokenClient(t, integration.WithNamespace(namespace.Key)) + + canReadAllIn(t, ctx, scopedClient, namespace.Key) if namespace.Key != "" { // ensure we exclude from reading in another namespace otherNS := integration.Namespaces.OtherNamespaceFrom(namespace.Expected) t.Run(fmt.Sprintf("NamespaceScopedIn(%q)", otherNS), func(t *testing.T) { - cannotReadAnyIn(t, ctx, opts.DefaultClient(t), otherNS) + cannotReadAnyIn(t, ctx, scopedClient, otherNS) }) } }) @@ -117,19 +114,6 @@ func Common(t *testing.T, opts integration.TestOpts) { }) } -func listFlagIsAllowed(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) { - t.Helper() - - t.Run(fmt.Sprintf("ListFlags(namespace: %q)", namespace), func(t *testing.T) { - // construct a new client using the previously obtained client token - resp, err := client.Flipt().ListFlags(ctx, &flipt.ListFlagRequest{ - NamespaceKey: namespace, - }) - require.NoError(t, err) - require.NotEmpty(t, resp.Flags) - }) -} - func listFlagsIsUnauthorized(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) { t.Helper() @@ -272,13 +256,13 @@ func ListSegments(in *flipt.ListSegmentRequest) clientCall { func assertIsAuthorized(t *testing.T, err error, authorized bool) { t.Helper() - expected := "rpc error: code = Unauthenticated desc = request was not authenticated" - if authorized { - if err != nil { - assert.NotEqual(t, err.Error(), expected) - } + if !authorized { + assert.Equal(t, codes.Unauthenticated, status.Code(err), err) return } - assert.EqualError(t, err, expected) + if err != nil { + code := status.Code(err) + assert.NotEqual(t, codes.Unauthenticated, code, err) + } } diff --git a/build/testing/integration/authz/auth.go b/build/testing/integration/authz/auth.go new file mode 100644 index 0000000000..324c2756d9 --- /dev/null +++ b/build/testing/integration/authz/auth.go @@ -0,0 +1,524 @@ +package authz + +import ( + "context" + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.flipt.io/flipt/build/testing/integration" + "go.flipt.io/flipt/rpc/flipt" + "go.flipt.io/flipt/rpc/flipt/auth" + sdk "go.flipt.io/flipt/sdk/go" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func Common(t *testing.T, opts integration.TestOpts) { + client := opts.TokenClient(t) + + t.Run("Authentication Methods", func(t *testing.T) { + ctx := context.Background() + + t.Run("List methods", func(t *testing.T) { + t.Log(`List methods (ensure at-least 1).`) + + methods, err := client.Auth().PublicAuthenticationService().ListAuthenticationMethods(ctx) + + require.NoError(t, err) + + assert.NotEmpty(t, methods) + }) + + t.Run("Get Self", func(t *testing.T) { + authn, err := client.Auth().AuthenticationService().GetAuthenticationSelf(ctx) + + require.NoError(t, err) + + assert.NotEmpty(t, authn.Id) + }) + + for _, namespace := range integration.Namespaces { + t.Run(fmt.Sprintf("InNamespace(%q)", namespace.Key), func(t *testing.T) { + for _, test := range []struct { + name string + client func(*testing.T, ...integration.ClientOpt) sdk.SDK + }{ + {"StaticToken", opts.TokenClient}, + {"JWT", opts.JWTClient}, + {"K8s", opts.K8sClient}, + } { + t.Run(test.name, func(t *testing.T) { + t.Run("NoRole", func(t *testing.T) { + // ensure we cannot do any read specific operations across namespaces + // with a token with no role + client := test.client(t) + cannotReadAnyIn(t, ctx, client, namespace.Key) + cannotWriteNamespaces(t, ctx, client) + cannotWriteNamespacedIn(t, ctx, client, namespace.Key) + }) + + t.Run("Admin", func(t *testing.T) { + // ensure admin can do all the things + client := test.client(t, integration.WithRole("admin")) + canReadAllIn(t, ctx, client, namespace.Key) + canWriteNamespaces(t, ctx, client) + canWriteNamespacedIn(t, ctx, client, namespace.Key) + }) + + t.Run("Editor", func(t *testing.T) { + client := test.client(t, integration.WithRole("editor")) + // can read everywhere (including namespaces) + canReadAllIn(t, ctx, client, namespace.Key) + // cannot write namespaces + cannotWriteNamespaces(t, ctx, client) + // but can write in namespaces + canWriteNamespacedIn(t, ctx, client, namespace.Key) + }) + + t.Run("Viewer", func(t *testing.T) { + client := test.client(t, integration.WithRole("viewer")) + // can read everywhere (including namespaces) + canReadAllIn(t, ctx, client, namespace.Key) + // cannot write namespaces + cannotWriteNamespaces(t, ctx, client) + // cannot write in namespaces either + cannotWriteNamespacedIn(t, ctx, client, namespace.Key) + }) + + t.Run("NamespacedViewer", func(t *testing.T) { + // ensure we cannot do read specific operations across other namespaces + // with the namespaced viewer role token + client := test.client(t, integration.WithRole(fmt.Sprintf("%s_viewer", namespace.Expected))) + // can read in designated namespace + canReadAllIn(t, ctx, client, namespace.Key) + // cannot read in other namespace + cannotReadAnyIn(t, ctx, client, integration.Namespaces.OtherNamespaceFrom(namespace.Expected)) + // cannot write namespaces + cannotWriteNamespaces(t, ctx, client) + // cannot write in namespaces either + cannotWriteNamespacedIn(t, ctx, client, namespace.Key) + }) + }) + } + }) + } + + t.Run("Expire Self", func(t *testing.T) { + err := client.Auth().AuthenticationService().ExpireAuthenticationSelf(ctx, &auth.ExpireAuthenticationSelfRequest{ + ExpiresAt: flipt.Now(), + }) + + require.NoError(t, err) + + t.Log(`Ensure token is no longer valid.`) + + _, err = client.Auth().AuthenticationService().GetAuthenticationSelf(ctx) + + status, ok := status.FromError(err) + require.True(t, ok) + assert.Equal(t, codes.Unauthenticated, status.Code()) + }) + }) +} + +func listFlagIsAllowed(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) { + t.Helper() + + t.Run(fmt.Sprintf("ListFlags(namespace: %q)", namespace), func(t *testing.T) { + // construct a new client using the previously obtained client token + resp, err := client.Flipt().ListFlags(ctx, &flipt.ListFlagRequest{ + NamespaceKey: namespace, + }) + require.NoError(t, err) + require.NotEmpty(t, resp.Flags) + }) +} + +func canReadAllIn(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) { + t.Run("CanReadAll", func(t *testing.T) { + clientCallSet{ + can(GetNamespace(&flipt.GetNamespaceRequest{Key: namespace})), + can(GetFlag(&flipt.GetFlagRequest{NamespaceKey: namespace, Key: "flag"})), + can(ListFlags(&flipt.ListFlagRequest{NamespaceKey: namespace})), + can(GetRule(&flipt.GetRuleRequest{NamespaceKey: namespace, FlagKey: "flag", Id: "id"})), + can(ListRules(&flipt.ListRuleRequest{NamespaceKey: namespace, FlagKey: "flag"})), + can(GetRollout(&flipt.GetRolloutRequest{NamespaceKey: namespace, FlagKey: "flag"})), + can(ListRollouts(&flipt.ListRolloutRequest{NamespaceKey: namespace, FlagKey: "flag"})), + can(GetSegment(&flipt.GetSegmentRequest{NamespaceKey: namespace, Key: "segment"})), + can(ListSegments(&flipt.ListSegmentRequest{NamespaceKey: namespace})), + }.assert(t, ctx, client) + }) +} + +func canWriteNamespaces(t *testing.T, ctx context.Context, client sdk.SDK) { + t.Run("CanWriteNamespaces", func(t *testing.T) { + namespace := fmt.Sprintf("%x", rand.Int63()) + clientCallSet{ + can(CreateNamespace(&flipt.CreateNamespaceRequest{Key: namespace, Name: namespace})), + can(UpdateNamespace(&flipt.UpdateNamespaceRequest{Key: namespace, Name: namespace})), + can(DeleteNamespace(&flipt.DeleteNamespaceRequest{Key: namespace})), + }.assert(t, ctx, client) + }) +} + +func cannotWriteNamespaces(t *testing.T, ctx context.Context, client sdk.SDK) { + t.Run("CannotWriteNamespaces", func(t *testing.T) { + namespace := fmt.Sprintf("%x", rand.Int63()) + clientCallSet{ + cannot(CreateNamespace(&flipt.CreateNamespaceRequest{Key: namespace, Name: namespace})), + cannot(UpdateNamespace(&flipt.UpdateNamespaceRequest{Key: namespace, Name: namespace})), + cannot(DeleteNamespace(&flipt.DeleteNamespaceRequest{Key: namespace})), + }.assert(t, ctx, client) + }) +} + +func canWriteNamespacedIn(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) { + t.Run("CanWriteNamespacedIn", func(t *testing.T) { + flag := fmt.Sprintf("%x", rand.Int63()) + segment := fmt.Sprintf("%x", rand.Int63()) + clientCallSet{ + can(CreateFlag(&flipt.CreateFlagRequest{NamespaceKey: namespace, Key: flag})), + can(UpdateFlag(&flipt.UpdateFlagRequest{NamespaceKey: namespace, Key: flag})), + can(CreateVariant(&flipt.CreateVariantRequest{NamespaceKey: namespace, FlagKey: flag, Key: flag})), + can(UpdateVariant(&flipt.UpdateVariantRequest{NamespaceKey: namespace, Id: "abcdef"})), + can(CreateSegment(&flipt.CreateSegmentRequest{NamespaceKey: namespace, Key: segment})), + can(UpdateSegment(&flipt.UpdateSegmentRequest{NamespaceKey: namespace, Key: segment})), + can(CreateConstraint(&flipt.CreateConstraintRequest{NamespaceKey: namespace, SegmentKey: segment})), + can(UpdateConstraint(&flipt.UpdateConstraintRequest{NamespaceKey: namespace, Id: "abcdef"})), + can(CreateRule(&flipt.CreateRuleRequest{NamespaceKey: namespace, FlagKey: flag, SegmentKey: segment})), + can(UpdateRule(&flipt.UpdateRuleRequest{NamespaceKey: namespace, Id: "abcdef"})), + can(OrderRules(&flipt.OrderRulesRequest{NamespaceKey: namespace, RuleIds: []string{"acdef"}})), + can(CreateRollout(&flipt.CreateRolloutRequest{NamespaceKey: namespace, FlagKey: flag})), + can(UpdateRollout(&flipt.UpdateRolloutRequest{NamespaceKey: namespace, Id: "abcdef"})), + can(OrderRollouts(&flipt.OrderRolloutsRequest{NamespaceKey: namespace, RolloutIds: []string{"acdef"}})), + // deletes + can(DeleteFlag(&flipt.DeleteFlagRequest{NamespaceKey: namespace, Key: flag})), + can(DeleteVariant(&flipt.DeleteVariantRequest{NamespaceKey: namespace, Id: "abcdef"})), + can(DeleteSegment(&flipt.DeleteSegmentRequest{NamespaceKey: namespace, Key: segment})), + can(DeleteConstraint(&flipt.DeleteConstraintRequest{NamespaceKey: namespace, Id: "abcdef"})), + can(DeleteRule(&flipt.DeleteRuleRequest{NamespaceKey: namespace, Id: "abcdef"})), + }.assert(t, ctx, client) + }) +} + +func cannotReadAnyIn(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) { + t.Run("CannotReadAny", func(t *testing.T) { + clientCallSet{ + cannot(GetNamespace(&flipt.GetNamespaceRequest{Key: namespace})), + cannot(GetFlag(&flipt.GetFlagRequest{NamespaceKey: namespace, Key: "flag"})), + cannot(ListFlags(&flipt.ListFlagRequest{NamespaceKey: namespace})), + cannot(GetRule(&flipt.GetRuleRequest{NamespaceKey: namespace, FlagKey: "flag", Id: "id"})), + cannot(ListRules(&flipt.ListRuleRequest{NamespaceKey: namespace, FlagKey: "flag"})), + cannot(GetRollout(&flipt.GetRolloutRequest{NamespaceKey: namespace, FlagKey: "flag"})), + cannot(ListRollouts(&flipt.ListRolloutRequest{NamespaceKey: namespace, FlagKey: "flag"})), + cannot(GetSegment(&flipt.GetSegmentRequest{NamespaceKey: namespace, Key: "segment"})), + cannot(ListSegments(&flipt.ListSegmentRequest{NamespaceKey: namespace})), + }.assert(t, ctx, client) + }) +} + +func cannotWriteNamespacedIn(t *testing.T, ctx context.Context, client sdk.SDK, namespace string) { + t.Run("CannotWriteNamespacedIn", func(t *testing.T) { + flag := fmt.Sprintf("%x", rand.Int63()) + segment := fmt.Sprintf("%x", rand.Int63()) + clientCallSet{ + cannot(CreateFlag(&flipt.CreateFlagRequest{NamespaceKey: namespace, Key: flag})), + cannot(UpdateFlag(&flipt.UpdateFlagRequest{NamespaceKey: namespace, Key: flag})), + cannot(CreateVariant(&flipt.CreateVariantRequest{NamespaceKey: namespace, FlagKey: flag, Key: flag})), + cannot(UpdateVariant(&flipt.UpdateVariantRequest{NamespaceKey: namespace, Id: "abcdef"})), + cannot(CreateSegment(&flipt.CreateSegmentRequest{NamespaceKey: namespace, Key: segment})), + cannot(UpdateSegment(&flipt.UpdateSegmentRequest{NamespaceKey: namespace, Key: segment})), + cannot(CreateConstraint(&flipt.CreateConstraintRequest{NamespaceKey: namespace, SegmentKey: segment})), + cannot(UpdateConstraint(&flipt.UpdateConstraintRequest{NamespaceKey: namespace, Id: "abcdef"})), + cannot(CreateRule(&flipt.CreateRuleRequest{NamespaceKey: namespace, FlagKey: flag, SegmentKey: segment})), + cannot(UpdateRule(&flipt.UpdateRuleRequest{NamespaceKey: namespace, Id: "abcdef"})), + cannot(OrderRules(&flipt.OrderRulesRequest{NamespaceKey: namespace, RuleIds: []string{"acdef"}})), + cannot(CreateRollout(&flipt.CreateRolloutRequest{NamespaceKey: namespace, FlagKey: flag})), + cannot(UpdateRollout(&flipt.UpdateRolloutRequest{NamespaceKey: namespace, Id: "abcdef"})), + cannot(OrderRollouts(&flipt.OrderRolloutsRequest{NamespaceKey: namespace, RolloutIds: []string{"acdef"}})), + // deletes + cannot(DeleteFlag(&flipt.DeleteFlagRequest{NamespaceKey: namespace, Key: flag})), + cannot(DeleteVariant(&flipt.DeleteVariantRequest{NamespaceKey: namespace, Id: "abcdef"})), + cannot(DeleteSegment(&flipt.DeleteSegmentRequest{NamespaceKey: namespace, Key: segment})), + cannot(DeleteConstraint(&flipt.DeleteConstraintRequest{NamespaceKey: namespace, Id: "abcdef"})), + cannot(DeleteRule(&flipt.DeleteRuleRequest{NamespaceKey: namespace, Id: "abcdef"})), + cannot(DeleteRollout(&flipt.DeleteRolloutRequest{NamespaceKey: namespace, Id: "abcdef"})), + }.assert(t, ctx, client) + }) +} + +type clientCallSet []isAuthorized + +type isAuthorized struct { + call clientCall + authorized bool +} + +func can(c clientCall) isAuthorized { return isAuthorized{c, true} } +func cannot(c clientCall) isAuthorized { return isAuthorized{c, false} } + +func (s clientCallSet) assert(t *testing.T, ctx context.Context, client sdk.SDK) { + for _, c := range s { + assertIsAuthorized(t, c.call(t, ctx, client), c.authorized) + } +} + +type clientCall func(*testing.T, context.Context, sdk.SDK) error + +func GetNamespace(in *flipt.GetNamespaceRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().GetNamespace(ctx, in) + return fmt.Errorf("GetNamespace: %w", err) + } +} + +func ListNamespaces(in *flipt.ListNamespaceRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().ListNamespaces(ctx, in) + return fmt.Errorf("ListNamespaces: %w", err) + } +} + +func CreateNamespace(in *flipt.CreateNamespaceRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateNamespace(ctx, in) + return fmt.Errorf("CreateNamespace: %w", err) + } +} + +func UpdateNamespace(in *flipt.UpdateNamespaceRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateNamespace(ctx, in) + return fmt.Errorf("UpdateNamespace: %w", err) + } +} + +func DeleteNamespace(in *flipt.DeleteNamespaceRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteNamespace: %w", s.Flipt().DeleteNamespace(ctx, in)) + } +} + +func GetFlag(in *flipt.GetFlagRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().GetFlag(ctx, in) + return fmt.Errorf("GetFlag: %w", err) + } +} + +func ListFlags(in *flipt.ListFlagRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().ListFlags(ctx, in) + return fmt.Errorf("ListFlags: %w", err) + } +} + +func CreateFlag(in *flipt.CreateFlagRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateFlag(ctx, in) + return fmt.Errorf("CreateFlag: %w", err) + } +} + +func UpdateFlag(in *flipt.UpdateFlagRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateFlag(ctx, in) + return fmt.Errorf("UpdateFlag: %w", err) + } +} + +func DeleteFlag(in *flipt.DeleteFlagRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteFlag: %w", s.Flipt().DeleteFlag(ctx, in)) + } +} + +func CreateVariant(in *flipt.CreateVariantRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateVariant(ctx, in) + return fmt.Errorf("CreateVariant: %w", err) + } +} + +func UpdateVariant(in *flipt.UpdateVariantRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateVariant(ctx, in) + return fmt.Errorf("UpdateVariant: %w", err) + } +} + +func DeleteVariant(in *flipt.DeleteVariantRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteVariant: %w", s.Flipt().DeleteVariant(ctx, in)) + } +} + +func GetRule(in *flipt.GetRuleRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().GetRule(ctx, in) + return fmt.Errorf("GetRule: %w", err) + } +} + +func ListRules(in *flipt.ListRuleRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().ListRules(ctx, in) + return fmt.Errorf("ListRules: %w", err) + } +} + +func CreateRule(in *flipt.CreateRuleRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateRule(ctx, in) + return fmt.Errorf("CreateRule: %w", err) + } +} + +func UpdateRule(in *flipt.UpdateRuleRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateRule(ctx, in) + return fmt.Errorf("UpdateRule: %w", err) + } +} + +func DeleteRule(in *flipt.DeleteRuleRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteRule: %w", s.Flipt().DeleteRule(ctx, in)) + } +} + +func OrderRules(in *flipt.OrderRulesRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("OrderRules: %w", s.Flipt().OrderRules(ctx, in)) + } +} + +func CreateDistribution(in *flipt.CreateDistributionRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateDistribution(ctx, in) + return fmt.Errorf("CreateDistribution: %w", err) + } +} + +func UpdateDistribution(in *flipt.UpdateDistributionRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateDistribution(ctx, in) + return fmt.Errorf("UpdateDistribution: %w", err) + } +} + +func DeleteDistribution(in *flipt.DeleteDistributionRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteDistribution: %w", s.Flipt().DeleteDistribution(ctx, in)) + } +} + +func CreateRollout(in *flipt.CreateRolloutRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateRollout(ctx, in) + return fmt.Errorf("CreateRollout: %w", err) + } +} +func UpdateRollout(in *flipt.UpdateRolloutRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateRollout(ctx, in) + return fmt.Errorf("UpdateRollout: %w", err) + } +} + +func DeleteRollout(in *flipt.DeleteRolloutRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteRollout: %w", s.Flipt().DeleteRollout(ctx, in)) + } +} + +func OrderRollouts(in *flipt.OrderRolloutsRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("OrderRollouts: %w", s.Flipt().OrderRollouts(ctx, in)) + } +} + +func GetRollout(in *flipt.GetRolloutRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().GetRollout(ctx, in) + return fmt.Errorf("GetRollout: %w", err) + } +} + +func ListRollouts(in *flipt.ListRolloutRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().ListRollouts(ctx, in) + return fmt.Errorf("ListRollouts: %w", err) + } +} + +func GetSegment(in *flipt.GetSegmentRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().GetSegment(ctx, in) + return fmt.Errorf("GetSegment: %w", err) + } +} + +func ListSegments(in *flipt.ListSegmentRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().ListSegments(ctx, in) + return fmt.Errorf("ListSegments: %w", err) + } +} + +func CreateSegment(in *flipt.CreateSegmentRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateSegment(ctx, in) + return fmt.Errorf("CreateSegment: %w", err) + } +} + +func UpdateSegment(in *flipt.UpdateSegmentRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateSegment(ctx, in) + return fmt.Errorf("UpdateSegment: %w", err) + } +} + +func DeleteSegment(in *flipt.DeleteSegmentRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteSegment: %w", s.Flipt().DeleteSegment(ctx, in)) + } +} + +func CreateConstraint(in *flipt.CreateConstraintRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().CreateConstraint(ctx, in) + return fmt.Errorf("CreateConstraint: %w", err) + } +} + +func UpdateConstraint(in *flipt.UpdateConstraintRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + _, err := s.Flipt().UpdateConstraint(ctx, in) + return fmt.Errorf("UpdateConstraint: %w", err) + } +} + +func DeleteConstraint(in *flipt.DeleteConstraintRequest) clientCall { + return func(t *testing.T, ctx context.Context, s sdk.SDK) error { + return fmt.Errorf("DeleteConstraint: %w", s.Flipt().DeleteConstraint(ctx, in)) + } +} + +func assertIsAuthorized(t *testing.T, err error, authorized bool) { + t.Helper() + if !authorized { + assert.Equal(t, codes.PermissionDenied, status.Code(err), err) + return + } + + if err != nil { + code := status.Code(err) + assert.NotEqual(t, codes.Unauthenticated, code, err) + assert.NotEqual(t, codes.PermissionDenied, code, err) + } +} diff --git a/build/testing/integration/authz/auth_test.go b/build/testing/integration/authz/auth_test.go new file mode 100644 index 0000000000..4d9c77a92d --- /dev/null +++ b/build/testing/integration/authz/auth_test.go @@ -0,0 +1,13 @@ +package authz + +import ( + "testing" + + "go.flipt.io/flipt/build/testing/integration" +) + +func TestAuthz(t *testing.T) { + integration.Harness(t, func(t *testing.T, opts integration.TestOpts) { + Common(t, opts) + }) +} diff --git a/build/testing/integration/integration.go b/build/testing/integration/integration.go index 5180916567..ac3a5b720e 100644 --- a/build/testing/integration/integration.go +++ b/build/testing/integration/integration.go @@ -1,9 +1,11 @@ package integration import ( + "context" "crypto/x509" "encoding/pem" "flag" + "fmt" "net/url" "os" "strings" @@ -13,6 +15,7 @@ import ( "github.com/go-jose/go-jose/v3" "github.com/go-jose/go-jose/v3/jwt" "github.com/stretchr/testify/require" + "go.flipt.io/flipt/rpc/flipt/auth" sdk "go.flipt.io/flipt/sdk/go" sdkgrpc "go.flipt.io/flipt/sdk/go/grpc" sdkhttp "go.flipt.io/flipt/sdk/go/http" @@ -99,33 +102,140 @@ func (o TestOpts) Protocol() Protocol { } func (o TestOpts) NoAuthClient(t *testing.T) sdk.SDK { + t.Helper() + return sdk.New(o.newTransport(t)) } -func (o TestOpts) DefaultClient(t *testing.T) sdk.SDK { +func (o TestOpts) bootstrapClient(t *testing.T) sdk.SDK { + t.Helper() + return sdk.New(o.newTransport(t), sdk.WithAuthenticationProvider( sdk.StaticTokenAuthenticationProvider(o.Token), )) } -func (o TestOpts) K8sClient(t *testing.T) sdk.SDK { +type ClientOpts struct { + Namespace string + Role string +} + +type ClientOpt func(*ClientOpts) + +func WithNamespace(ns string) ClientOpt { + return func(co *ClientOpts) { + co.Namespace = ns + } +} + +func WithRole(role string) ClientOpt { + return func(co *ClientOpts) { + co.Role = role + } +} + +func (o TestOpts) TokenClient(t *testing.T, opts ...ClientOpt) sdk.SDK { + t.Helper() + + var copts ClientOpts + for _, opt := range opts { + opt(&copts) + } + + metadata := map[string]string{} + if copts.Role != "" { + metadata["io.flipt.auth.role"] = copts.Role + } + + resp, err := o.bootstrapClient(t).Auth().AuthenticationMethodTokenService().CreateToken(context.Background(), &auth.CreateTokenRequest{ + Name: t.Name(), + NamespaceKey: copts.Namespace, + Metadata: metadata, + }) + require.NoError(t, err) + + return sdk.New(o.newTransport(t), sdk.WithAuthenticationProvider( + sdk.StaticTokenAuthenticationProvider(resp.ClientToken), + )) +} + +func (o TestOpts) K8sClient(t *testing.T, opts ...ClientOpt) sdk.SDK { + t.Helper() + + var copts ClientOpts + for _, opt := range opts { + opt(&copts) + } + + saName := "integration-test" + if copts.Role != "" { + saName = copts.Role + } + + saToken := signWithPrivateKeyClaims(t, "/var/run/secrets/flipt/k8s.pem", map[string]any{ + "exp": time.Now().Add(24 * time.Hour).Unix(), + "iss": "https://discover.svc", + "kubernetes.io": map[string]any{ + "namespace": "integration", + "pod": map[string]any{ + "name": "integration-test-7d26f049-kdurb", + "uid": "bd8299f9-c50f-4b76-af33-9d8e3ef2b850", + }, + "serviceaccount": map[string]any{ + "name": saName, + "uid": "4f18914e-f276-44b2-aebd-27db1d8f8def", + }, + }, + }) + + // write out JWT into service account token slot + require.NoError(t, os.MkdirAll("/var/run/secrets/kubernetes.io/serviceaccount", 0755)) + + tokenPath := fmt.Sprintf("/var/run/secrets/kubernetes.io/serviceaccount/%s.token", saName) + require.NoError(t, os.WriteFile(tokenPath, []byte(saToken), 0644)) + transport := o.newTransport(t) return sdk.New(transport, sdk.WithAuthenticationProvider( - sdk.NewKubernetesAuthenticationProvider(transport), + sdk.NewKubernetesAuthenticationProvider(transport, sdk.WithKubernetesServiceAccountTokenPath(tokenPath)), )) } -func (o TestOpts) JWTClient(t *testing.T) sdk.SDK { - bytes, err := os.ReadFile("/var/run/secrets/flipt/private.pem") - if err != nil { - t.Fatal(err) +func (o TestOpts) JWTClient(t *testing.T, opts ...ClientOpt) sdk.SDK { + t.Helper() + + var copts ClientOpts + for _, opt := range opts { + opt(&copts) + } + + claims := map[string]any{ + "iss": "https://flipt.io", + "iat": time.Now().Unix(), + "exp": time.Now().Add(3 * time.Minute).Unix(), + } + + if copts.Role != "" { + claims["io.flipt.auth.role"] = copts.Role + } + + if copts.Namespace != "" { + claims["io.flipt.auth.namespace"] = copts.Namespace } + return sdk.New(o.newTransport(t), sdk.WithAuthenticationProvider( + sdk.JWTAuthenticationProvider(signWithPrivateKeyClaims(t, "/var/run/secrets/flipt/jwt.pem", claims)), + )) +} + +func signWithPrivateKeyClaims(t *testing.T, privPath string, claims map[string]any) string { + t.Helper() + + bytes, err := os.ReadFile(privPath) + require.NoError(t, err) + block, _ := pem.Decode(bytes) key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) sig, err := jose.NewSigner( jose.SigningKey{Algorithm: jose.RS256, Key: key}, @@ -133,19 +243,11 @@ func (o TestOpts) JWTClient(t *testing.T) sdk.SDK { ) raw, err := jwt.Signed(sig). - Claims(map[string]any{ - "iss": "https://flipt.io", - "iat": time.Now().Unix(), - "exp": time.Now().Add(3 * time.Minute).Unix(), - }). + Claims(claims). CompactSerialize() - if err != nil { - panic(err) - } + require.NoError(t, err) - return sdk.New(o.newTransport(t), sdk.WithAuthenticationProvider( - sdk.JWTAuthenticationProvider(raw), - )) + return raw } func (o TestOpts) newTransport(t *testing.T) (transport sdk.Transport) { diff --git a/build/testing/integration/readonly/readonly_test.go b/build/testing/integration/readonly/readonly_test.go index 9572e723ea..95108eb144 100644 --- a/build/testing/integration/readonly/readonly_test.go +++ b/build/testing/integration/readonly/readonly_test.go @@ -19,7 +19,7 @@ func TestReadOnly(t *testing.T) { integration.Harness(t, func(t *testing.T, opts integration.TestOpts) { var ( ctx = context.Background() - sdk = opts.DefaultClient(t) + sdk = opts.TokenClient(t) ) ns, err := sdk.Flipt().GetNamespace(ctx, &flipt.GetNamespaceRequest{ diff --git a/config/flipt.schema.cue b/config/flipt.schema.cue index 750b97bb1c..127dfaa5f1 100644 --- a/config/flipt.schema.cue +++ b/config/flipt.schema.cue @@ -51,6 +51,7 @@ import "strings" bootstrap?: { token?: string expiration: =~#duration | int + metadata?: [string]: string } } @@ -163,7 +164,7 @@ import "strings" #cloud: { host?: string | *"flipt.cloud" organization?: string - gateway?: string + gateway?: string authentication?: { api_key?: string } @@ -358,9 +359,9 @@ import "strings" #audit: { sinks?: { log?: { - enabled?: bool | *false - file?: string | *"" - encoding?: *"" | "json" | "console" + enabled?: bool | *false + file?: string | *"" + encoding?: *"" | "json" | "console" } webhook?: { enabled?: bool | *false diff --git a/config/flipt.schema.json b/config/flipt.schema.json index 7bf9419ee0..61c06075c2 100644 --- a/config/flipt.schema.json +++ b/config/flipt.schema.json @@ -123,6 +123,10 @@ "type": "integer" } ] + }, + "metadata": { + "type": ["object", "null"], + "additionalProperties": true } } } diff --git a/internal/cmd/authn.go b/internal/cmd/authn.go index cf5b80f590..f5157a9cf7 100644 --- a/internal/cmd/authn.go +++ b/internal/cmd/authn.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "regexp" + "strings" "github.com/go-chi/chi/v5" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector" @@ -149,6 +150,11 @@ func authenticationGRPC( opts = append(opts, storageauth.WithExpiration(authCfg.Methods.Token.Method.Bootstrap.Expiration)) } + // add any additional metadata if defined + for k, v := range authCfg.Methods.Token.Method.Bootstrap.Metadata { + opts = append(opts, storageauth.WithMetadataAttribute(strings.ToLower(k), v)) + } + // attempt to bootstrap authentication store clientToken, err := storageauth.Bootstrap(ctx, store, opts...) if err != nil { diff --git a/internal/cmd/grpc.go b/internal/cmd/grpc.go index 47efccd9c9..2c4ccbc511 100644 --- a/internal/cmd/grpc.go +++ b/internal/cmd/grpc.go @@ -339,7 +339,6 @@ func NewGRPCServer( interceptors = append(interceptors, append(authInterceptors, middlewaregrpc.ErrorUnaryInterceptor, - middlewaregrpc.ValidationUnaryInterceptor, middlewaregrpc.FliptAcceptServerVersionUnaryInterceptor(logger), middlewaregrpc.EvaluationUnaryInterceptor(cfg.Analytics.Enabled()), )..., @@ -483,6 +482,9 @@ func NewGRPCServer( logger.Info("authorization middleware enabled") } + // we validate requests before cache but after authn and authz + interceptors = append(interceptors, middlewaregrpc.ValidationUnaryInterceptor) + grpcOpts := []grpc.ServerOption{ grpc.ChainUnaryInterceptor(interceptors...), grpc.KeepaliveParams(keepalive.ServerParameters{ diff --git a/internal/config/authentication.go b/internal/config/authentication.go index 99f6ec3073..c4dafc6139 100644 --- a/internal/config/authentication.go +++ b/internal/config/authentication.go @@ -426,8 +426,9 @@ func (a AuthenticationMethodTokenConfig) validate() error { return nil } // AuthenticationMethodTokenBootstrapConfig contains fields used to configure the // bootstrap process for the authentication method "token". type AuthenticationMethodTokenBootstrapConfig struct { - Token string `json:"-" mapstructure:"token" yaml:"token"` - Expiration time.Duration `json:"expiration,omitempty" mapstructure:"expiration" yaml:"expiration,omitempty"` + Token string `json:"-" mapstructure:"token" yaml:"token"` + Expiration time.Duration `json:"expiration,omitempty" mapstructure:"expiration" yaml:"expiration,omitempty"` + Metadata map[string]string `json:"metadata,omitempty" mapstructure:"metadata" yaml:"metadata,omitempty"` } // AuthenticationMethodOIDCConfig configures the OIDC authentication method. diff --git a/internal/config/config.go b/internal/config/config.go index 1b880f17ae..1e67dde43e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -402,8 +402,8 @@ func (c *Config) validate() (err error) { } } - if c.Authorization.Required && !(c.Authentication.Required && c.Authentication.SessionEnabled()) { - return fmt.Errorf("authorization requires authentication and a session enabled method") + if c.Authorization.Required && !c.Authentication.Required { + return fmt.Errorf("authorization requires authentication also be required") } return nil diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ae4ff3061f..271a8690d7 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -639,12 +639,7 @@ func TestLoad(t *testing.T) { { name: "authorization required without authentication", path: "./testdata/authorization/authentication_not_required.yml", - wantErr: errors.New("authorization requires authentication and a session enabled method"), - }, - { - name: "authorization required without session enabled authentication", - path: "./testdata/authorization/authentication_not_session_enabled.yml", - wantErr: errors.New("authorization requires authentication and a session enabled method"), + wantErr: errors.New("authorization requires authentication also be required"), }, { name: "authorization with all authentication methods enabled", diff --git a/internal/config/testdata/authorization/authentication_not_session_enabled.yml b/internal/config/testdata/authorization/authentication_not_session_enabled.yml deleted file mode 100644 index fa621da563..0000000000 --- a/internal/config/testdata/authorization/authentication_not_session_enabled.yml +++ /dev/null @@ -1,12 +0,0 @@ -authentication: - required: true - methods: - token: - enabled: false - -authorization: - required: true - -experimental: - authorization: - enabled: true diff --git a/internal/server/authn/method/token/server.go b/internal/server/authn/method/token/server.go index 059dc54e98..f15e6d8b9e 100644 --- a/internal/server/authn/method/token/server.go +++ b/internal/server/authn/method/token/server.go @@ -49,13 +49,16 @@ func (s *Server) AllowsNamespaceScopedAuthentication(ctx context.Context) bool { // Given the token is created successfully, the generate clientToken string is returned. // Along with the created Authentication, which includes it's identifier and associated timestamps. func (s *Server) CreateToken(ctx context.Context, req *auth.CreateTokenRequest) (*auth.CreateTokenResponse, error) { - metadata := map[string]string{ - storageMetadataNameKey: req.GetName(), + metadata := req.Metadata + if metadata == nil { + metadata = map[string]string{} } + metadata[storageMetadataNameKey] = req.GetName() if req.GetDescription() != "" { metadata[storageMetadataDescriptionKey] = req.GetDescription() } + if req.GetNamespaceKey() != "" { metadata[storageMetadataNamespaceKey] = req.GetNamespaceKey() } diff --git a/internal/server/authn/method/token/server_test.go b/internal/server/authn/method/token/server_test.go index dc3e33ef91..8fe8662885 100644 --- a/internal/server/authn/method/token/server_test.go +++ b/internal/server/authn/method/token/server_test.go @@ -14,6 +14,7 @@ import ( "go.uber.org/zap/zaptest" "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" "google.golang.org/protobuf/testing/protocmp" @@ -56,7 +57,7 @@ func TestServer(t *testing.T) { } ) - conn, err := grpc.DialContext(ctx, "", grpc.WithInsecure(), grpc.WithContextDialer(dialer)) + conn, err := grpc.NewClient("passthrough://local", grpc.WithContextDialer(dialer), grpc.WithTransportCredentials(insecure.NewCredentials())) require.NoError(t, err) defer conn.Close() @@ -67,6 +68,9 @@ func TestServer(t *testing.T) { Name: "access_all_areas", Description: "Super secret skeleton key", NamespaceKey: "my-namespace", + Metadata: map[string]string{ + "foo": "bar", + }, }) require.NoError(t, err) @@ -75,6 +79,7 @@ func TestServer(t *testing.T) { assert.Equal(t, "access_all_areas", metadata["io.flipt.auth.token.name"]) assert.Equal(t, "Super secret skeleton key", metadata["io.flipt.auth.token.description"]) assert.Equal(t, "my-namespace", metadata["io.flipt.auth.token.namespace"]) + assert.Equal(t, "bar", metadata["foo"]) // ensure client token can be used on store to fetch authentication // and that the authentication returned matches the one received diff --git a/internal/server/authn/middleware/grpc/middleware.go b/internal/server/authn/middleware/grpc/middleware.go index e1e8e63799..b208ff5aba 100644 --- a/internal/server/authn/middleware/grpc/middleware.go +++ b/internal/server/authn/middleware/grpc/middleware.go @@ -198,22 +198,32 @@ func JWTAuthenticationInterceptor(logger *zap.Logger, validator jwt.Validator, e metadata := map[string]string{} - userClaims, ok := jwtClaims["user"].(map[string]interface{}) - if ok { - for _, fields := range [][2]string{ - {"email", "email"}, - {"sub", "sub"}, - {"image", "picture"}, - {"name", "name"}, - } { - if v, ok := userClaims[fields[0]]; ok { - metadata[fmt.Sprintf("io.flipt.auth.jwt.%s", fields[1])] = fmt.Sprintf("%v", v) - } + for k, v := range jwtClaims { + if strings.HasPrefix(k, "io.flipt.auth") { + metadata[k] = fmt.Sprintf("%v", v) + continue + } + + if v, ok := v.(string); ok && k == "iss" { + metadata["io.flipt.auth.jwt.issuer"] = v + continue } - } - if iss, ok := jwtClaims["iss"].(string); ok { - metadata["io.flipt.auth.jwt.issuer"] = iss + if k == "user" { + userClaims, ok := v.(map[string]interface{}) + if ok { + for _, fields := range [][2]string{ + {"email", "email"}, + {"sub", "sub"}, + {"image", "picture"}, + {"name", "name"}, + } { + if v, ok := userClaims[fields[0]]; ok { + metadata[fmt.Sprintf("io.flipt.auth.jwt.%s", fields[1])] = fmt.Sprintf("%v", v) + } + } + } + } } auth := &authrpc.Authentication{ diff --git a/internal/server/authn/middleware/grpc/middleware_test.go b/internal/server/authn/middleware/grpc/middleware_test.go index 4c26f815c1..6e6464dc68 100644 --- a/internal/server/authn/middleware/grpc/middleware_test.go +++ b/internal/server/authn/middleware/grpc/middleware_test.go @@ -122,6 +122,41 @@ func TestJWTAuthenticationInterceptor(t *testing.T) { "io.flipt.auth.jwt.issuer": "flipt.io", }, }, + { + name: "successful authentication (with custom user claims and arbitrary role)", + metadataFunc: func() metadata.MD { + claims := map[string]interface{}{ + "iss": "flipt.io", + "aud": "flipt", + "iat": nowUnix, + "exp": futureUnix, + "user": map[string]string{ + "sub": "sub", + "email": "email", + "image": "image", + "name": "name", + }, + "io.flipt.auth.role": "admin", + } + + token := oidc.TestSignJWT(t, priv, string(jwt.RS256), claims, []byte("test-key")) + return metadata.MD{ + "Authorization": []string{"JWT " + token}, + } + }, + expectedJWT: jwt.Expected{ + Issuer: "flipt.io", + Audiences: []string{"flipt"}, + }, + expectedMetadata: map[string]string{ + "io.flipt.auth.jwt.sub": "sub", + "io.flipt.auth.jwt.email": "email", + "io.flipt.auth.jwt.picture": "image", + "io.flipt.auth.jwt.name": "name", + "io.flipt.auth.jwt.issuer": "flipt.io", + "io.flipt.auth.role": "admin", + }, + }, { name: "invalid issuer", metadataFunc: func() metadata.MD { diff --git a/internal/storage/authn/bootstrap.go b/internal/storage/authn/bootstrap.go index 2b9ad84532..df2782e23f 100644 --- a/internal/storage/authn/bootstrap.go +++ b/internal/storage/authn/bootstrap.go @@ -13,6 +13,7 @@ import ( type bootstrapOpt struct { token string expiration time.Duration + metadata map[string]string } // BootstrapOption is a type which configures the bootstrap or initial static token. @@ -32,10 +33,23 @@ func WithExpiration(expiration time.Duration) BootstrapOption { } } +// WithMetadataAttribute can be used to add additional metadata k/v pairs +// to the resulting bootstrap token +func WithMetadataAttribute(key, value string) BootstrapOption { + return func(bo *bootstrapOpt) { + bo.metadata[key] = value + } +} + // Bootstrap creates an initial static authentication of type token // if one does not already exist. func Bootstrap(ctx context.Context, store Store, opts ...BootstrapOption) (string, error) { - var o bootstrapOpt + o := bootstrapOpt{ + metadata: map[string]string{ + "io.flipt.auth.token.name": "initial_bootstrap_token", + "io.flipt.auth.token.description": "Initial token created when bootstrapping authentication", + }, + } for _, opt := range opts { opt(&o) } @@ -51,11 +65,8 @@ func Bootstrap(ctx context.Context, store Store, opts ...BootstrapOption) (strin } req := &CreateAuthenticationRequest{ - Method: rpcauth.Method_METHOD_TOKEN, - Metadata: map[string]string{ - "io.flipt.auth.token.name": "initial_bootstrap_token", - "io.flipt.auth.token.description": "Initial token created when bootstrapping authentication", - }, + Method: rpcauth.Method_METHOD_TOKEN, + Metadata: o.metadata, } // if a client token is provided, use it diff --git a/rpc/flipt/auth/auth.pb.go b/rpc/flipt/auth/auth.pb.go index 038753ff75..fd48e7f668 100644 --- a/rpc/flipt/auth/auth.pb.go +++ b/rpc/flipt/auth/auth.pb.go @@ -557,6 +557,7 @@ type CreateTokenRequest struct { Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` NamespaceKey string `protobuf:"bytes,4,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *CreateTokenRequest) Reset() { @@ -619,6 +620,13 @@ func (x *CreateTokenRequest) GetNamespaceKey() string { return "" } +func (x *CreateTokenRequest) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + type CreateTokenResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1077,7 +1085,7 @@ var file_auth_auth_proto_rawDesc = []byte{ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x65, 0x78, 0x70, 0x69, - 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x22, 0xaa, 0x01, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x22, 0xb1, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, @@ -1088,141 +1096,150 @@ var file_auth_auth_proto_rawDesc = []byte{ 0x61, 0x6d, 0x70, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x4b, 0x65, 0x79, 0x22, 0x7c, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x42, 0x0a, - 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0x47, 0x0a, 0x13, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, - 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3b, 0x0a, 0x14, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x55, 0x72, 0x6c, 0x22, 0x57, 0x0a, 0x0f, 0x43, 0x61, 0x6c, 0x6c, 0x62, - 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x22, 0x79, 0x0a, 0x10, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x42, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, - 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x51, 0x0a, 0x1b, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x85, - 0x01, 0x0a, 0x1c, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x42, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x88, 0x01, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, - 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x4f, 0x4b, - 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, - 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, - 0x4b, 0x55, 0x42, 0x45, 0x52, 0x4e, 0x45, 0x54, 0x45, 0x53, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, - 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x47, 0x49, 0x54, 0x48, 0x55, 0x42, 0x10, 0x04, 0x12, - 0x0e, 0x0a, 0x0a, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4a, 0x57, 0x54, 0x10, 0x05, 0x12, - 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x10, - 0x06, 0x32, 0x83, 0x01, 0x0a, 0x1b, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, - 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x64, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2d, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, - 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xe7, 0x03, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, - 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x4d, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x4b, 0x65, 0x79, 0x12, 0x48, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, + 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7c, 0x0a, 0x13, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x42, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x47, 0x0a, 0x13, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x22, 0x3b, 0x0a, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, + 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x72, 0x6c, 0x22, 0x57, + 0x0a, 0x0f, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, + 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x79, 0x0a, 0x10, 0x43, 0x61, 0x6c, 0x6c, 0x62, + 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x42, + 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x51, 0x0a, 0x1b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x85, 0x01, 0x0a, 0x1c, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x42, 0x0a, 0x0e, 0x61, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x88, 0x01, + 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, 0x54, 0x48, + 0x4f, 0x44, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x54, + 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, + 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, + 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4b, 0x55, 0x42, 0x45, 0x52, 0x4e, 0x45, 0x54, 0x45, + 0x53, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x47, 0x49, + 0x54, 0x48, 0x55, 0x42, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, + 0x5f, 0x4a, 0x57, 0x54, 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, + 0x5f, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x10, 0x06, 0x32, 0x83, 0x01, 0x0a, 0x1b, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2d, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xe7, + 0x03, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4d, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, + 0x66, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, + 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, - 0x12, 0x57, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x13, 0x4c, 0x69, 0x73, + 0x12, 0x68, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x26, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, - 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, - 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x61, - 0x0a, 0x18, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, 0x66, 0x12, 0x2b, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, 0x66, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x00, 0x32, 0x74, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x50, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, - 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, - 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xbf, 0x01, 0x0a, 0x1f, 0x41, 0x75, 0x74, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x14, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x61, 0x0a, 0x18, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, + 0x66, 0x12, 0x2b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x45, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x74, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x4f, 0x49, 0x44, 0x43, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x12, 0x1f, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x47, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1b, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, - 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x94, 0x01, 0x0a, 0x25, 0x41, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x66, 0x6c, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x50, 0x0a, 0x0b, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xbf, + 0x01, 0x0a, 0x1f, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x49, 0x44, 0x43, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, + 0x52, 0x4c, 0x12, 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, 0x62, + 0x61, 0x63, 0x6b, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, + 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x32, 0x94, 0x01, 0x0a, 0x25, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, + 0x74, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x14, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, - 0x68, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x32, 0xc1, 0x01, 0x0a, 0x21, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, - 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x12, 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, - 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, - 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x08, 0x43, - 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, - 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2e, 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xc1, 0x01, 0x0a, 0x21, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, + 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x12, 0x1f, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x47, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1b, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x22, 0x5a, 0x20, 0x67, + 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1238,7 +1255,7 @@ func file_auth_auth_proto_rawDescGZIP() []byte { } var file_auth_auth_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_auth_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_auth_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_auth_auth_proto_goTypes = []interface{}{ (Method)(0), // 0: flipt.auth.Method (*MethodInfo)(nil), // 1: flipt.auth.MethodInfo @@ -1258,55 +1275,57 @@ var file_auth_auth_proto_goTypes = []interface{}{ (*VerifyServiceAccountRequest)(nil), // 15: flipt.auth.VerifyServiceAccountRequest (*VerifyServiceAccountResponse)(nil), // 16: flipt.auth.VerifyServiceAccountResponse nil, // 17: flipt.auth.Authentication.MetadataEntry - (*structpb.Struct)(nil), // 18: google.protobuf.Struct - (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 20: google.protobuf.Empty + nil, // 18: flipt.auth.CreateTokenRequest.MetadataEntry + (*structpb.Struct)(nil), // 19: google.protobuf.Struct + (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 21: google.protobuf.Empty } var file_auth_auth_proto_depIdxs = []int32{ 0, // 0: flipt.auth.MethodInfo.method:type_name -> flipt.auth.Method - 18, // 1: flipt.auth.MethodInfo.metadata:type_name -> google.protobuf.Struct + 19, // 1: flipt.auth.MethodInfo.metadata:type_name -> google.protobuf.Struct 1, // 2: flipt.auth.ListAuthenticationMethodsResponse.methods:type_name -> flipt.auth.MethodInfo 0, // 3: flipt.auth.Authentication.method:type_name -> flipt.auth.Method - 19, // 4: flipt.auth.Authentication.expires_at:type_name -> google.protobuf.Timestamp - 19, // 5: flipt.auth.Authentication.created_at:type_name -> google.protobuf.Timestamp - 19, // 6: flipt.auth.Authentication.updated_at:type_name -> google.protobuf.Timestamp + 20, // 4: flipt.auth.Authentication.expires_at:type_name -> google.protobuf.Timestamp + 20, // 5: flipt.auth.Authentication.created_at:type_name -> google.protobuf.Timestamp + 20, // 6: flipt.auth.Authentication.updated_at:type_name -> google.protobuf.Timestamp 17, // 7: flipt.auth.Authentication.metadata:type_name -> flipt.auth.Authentication.MetadataEntry 0, // 8: flipt.auth.ListAuthenticationsRequest.method:type_name -> flipt.auth.Method 3, // 9: flipt.auth.ListAuthenticationsResponse.authentications:type_name -> flipt.auth.Authentication - 19, // 10: flipt.auth.ExpireAuthenticationSelfRequest.expires_at:type_name -> google.protobuf.Timestamp - 19, // 11: flipt.auth.CreateTokenRequest.expires_at:type_name -> google.protobuf.Timestamp - 3, // 12: flipt.auth.CreateTokenResponse.authentication:type_name -> flipt.auth.Authentication - 3, // 13: flipt.auth.CallbackResponse.authentication:type_name -> flipt.auth.Authentication - 3, // 14: flipt.auth.VerifyServiceAccountResponse.authentication:type_name -> flipt.auth.Authentication - 20, // 15: flipt.auth.PublicAuthenticationService.ListAuthenticationMethods:input_type -> google.protobuf.Empty - 20, // 16: flipt.auth.AuthenticationService.GetAuthenticationSelf:input_type -> google.protobuf.Empty - 4, // 17: flipt.auth.AuthenticationService.GetAuthentication:input_type -> flipt.auth.GetAuthenticationRequest - 5, // 18: flipt.auth.AuthenticationService.ListAuthentications:input_type -> flipt.auth.ListAuthenticationsRequest - 7, // 19: flipt.auth.AuthenticationService.DeleteAuthentication:input_type -> flipt.auth.DeleteAuthenticationRequest - 8, // 20: flipt.auth.AuthenticationService.ExpireAuthenticationSelf:input_type -> flipt.auth.ExpireAuthenticationSelfRequest - 9, // 21: flipt.auth.AuthenticationMethodTokenService.CreateToken:input_type -> flipt.auth.CreateTokenRequest - 11, // 22: flipt.auth.AuthenticationMethodOIDCService.AuthorizeURL:input_type -> flipt.auth.AuthorizeURLRequest - 13, // 23: flipt.auth.AuthenticationMethodOIDCService.Callback:input_type -> flipt.auth.CallbackRequest - 15, // 24: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount:input_type -> flipt.auth.VerifyServiceAccountRequest - 11, // 25: flipt.auth.AuthenticationMethodGithubService.AuthorizeURL:input_type -> flipt.auth.AuthorizeURLRequest - 13, // 26: flipt.auth.AuthenticationMethodGithubService.Callback:input_type -> flipt.auth.CallbackRequest - 2, // 27: flipt.auth.PublicAuthenticationService.ListAuthenticationMethods:output_type -> flipt.auth.ListAuthenticationMethodsResponse - 3, // 28: flipt.auth.AuthenticationService.GetAuthenticationSelf:output_type -> flipt.auth.Authentication - 3, // 29: flipt.auth.AuthenticationService.GetAuthentication:output_type -> flipt.auth.Authentication - 6, // 30: flipt.auth.AuthenticationService.ListAuthentications:output_type -> flipt.auth.ListAuthenticationsResponse - 20, // 31: flipt.auth.AuthenticationService.DeleteAuthentication:output_type -> google.protobuf.Empty - 20, // 32: flipt.auth.AuthenticationService.ExpireAuthenticationSelf:output_type -> google.protobuf.Empty - 10, // 33: flipt.auth.AuthenticationMethodTokenService.CreateToken:output_type -> flipt.auth.CreateTokenResponse - 12, // 34: flipt.auth.AuthenticationMethodOIDCService.AuthorizeURL:output_type -> flipt.auth.AuthorizeURLResponse - 14, // 35: flipt.auth.AuthenticationMethodOIDCService.Callback:output_type -> flipt.auth.CallbackResponse - 16, // 36: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount:output_type -> flipt.auth.VerifyServiceAccountResponse - 12, // 37: flipt.auth.AuthenticationMethodGithubService.AuthorizeURL:output_type -> flipt.auth.AuthorizeURLResponse - 14, // 38: flipt.auth.AuthenticationMethodGithubService.Callback:output_type -> flipt.auth.CallbackResponse - 27, // [27:39] is the sub-list for method output_type - 15, // [15:27] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 20, // 10: flipt.auth.ExpireAuthenticationSelfRequest.expires_at:type_name -> google.protobuf.Timestamp + 20, // 11: flipt.auth.CreateTokenRequest.expires_at:type_name -> google.protobuf.Timestamp + 18, // 12: flipt.auth.CreateTokenRequest.metadata:type_name -> flipt.auth.CreateTokenRequest.MetadataEntry + 3, // 13: flipt.auth.CreateTokenResponse.authentication:type_name -> flipt.auth.Authentication + 3, // 14: flipt.auth.CallbackResponse.authentication:type_name -> flipt.auth.Authentication + 3, // 15: flipt.auth.VerifyServiceAccountResponse.authentication:type_name -> flipt.auth.Authentication + 21, // 16: flipt.auth.PublicAuthenticationService.ListAuthenticationMethods:input_type -> google.protobuf.Empty + 21, // 17: flipt.auth.AuthenticationService.GetAuthenticationSelf:input_type -> google.protobuf.Empty + 4, // 18: flipt.auth.AuthenticationService.GetAuthentication:input_type -> flipt.auth.GetAuthenticationRequest + 5, // 19: flipt.auth.AuthenticationService.ListAuthentications:input_type -> flipt.auth.ListAuthenticationsRequest + 7, // 20: flipt.auth.AuthenticationService.DeleteAuthentication:input_type -> flipt.auth.DeleteAuthenticationRequest + 8, // 21: flipt.auth.AuthenticationService.ExpireAuthenticationSelf:input_type -> flipt.auth.ExpireAuthenticationSelfRequest + 9, // 22: flipt.auth.AuthenticationMethodTokenService.CreateToken:input_type -> flipt.auth.CreateTokenRequest + 11, // 23: flipt.auth.AuthenticationMethodOIDCService.AuthorizeURL:input_type -> flipt.auth.AuthorizeURLRequest + 13, // 24: flipt.auth.AuthenticationMethodOIDCService.Callback:input_type -> flipt.auth.CallbackRequest + 15, // 25: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount:input_type -> flipt.auth.VerifyServiceAccountRequest + 11, // 26: flipt.auth.AuthenticationMethodGithubService.AuthorizeURL:input_type -> flipt.auth.AuthorizeURLRequest + 13, // 27: flipt.auth.AuthenticationMethodGithubService.Callback:input_type -> flipt.auth.CallbackRequest + 2, // 28: flipt.auth.PublicAuthenticationService.ListAuthenticationMethods:output_type -> flipt.auth.ListAuthenticationMethodsResponse + 3, // 29: flipt.auth.AuthenticationService.GetAuthenticationSelf:output_type -> flipt.auth.Authentication + 3, // 30: flipt.auth.AuthenticationService.GetAuthentication:output_type -> flipt.auth.Authentication + 6, // 31: flipt.auth.AuthenticationService.ListAuthentications:output_type -> flipt.auth.ListAuthenticationsResponse + 21, // 32: flipt.auth.AuthenticationService.DeleteAuthentication:output_type -> google.protobuf.Empty + 21, // 33: flipt.auth.AuthenticationService.ExpireAuthenticationSelf:output_type -> google.protobuf.Empty + 10, // 34: flipt.auth.AuthenticationMethodTokenService.CreateToken:output_type -> flipt.auth.CreateTokenResponse + 12, // 35: flipt.auth.AuthenticationMethodOIDCService.AuthorizeURL:output_type -> flipt.auth.AuthorizeURLResponse + 14, // 36: flipt.auth.AuthenticationMethodOIDCService.Callback:output_type -> flipt.auth.CallbackResponse + 16, // 37: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount:output_type -> flipt.auth.VerifyServiceAccountResponse + 12, // 38: flipt.auth.AuthenticationMethodGithubService.AuthorizeURL:output_type -> flipt.auth.AuthorizeURLResponse + 14, // 39: flipt.auth.AuthenticationMethodGithubService.Callback:output_type -> flipt.auth.CallbackResponse + 28, // [28:40] is the sub-list for method output_type + 16, // [16:28] is the sub-list for method input_type + 16, // [16:16] is the sub-list for extension type_name + 16, // [16:16] is the sub-list for extension extendee + 0, // [0:16] is the sub-list for field type_name } func init() { file_auth_auth_proto_init() } @@ -1515,7 +1534,7 @@ func file_auth_auth_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_auth_auth_proto_rawDesc, NumEnums: 1, - NumMessages: 17, + NumMessages: 18, NumExtensions: 0, NumServices: 6, }, diff --git a/rpc/flipt/auth/auth.proto b/rpc/flipt/auth/auth.proto index caf93c4bf3..63571505ca 100644 --- a/rpc/flipt/auth/auth.proto +++ b/rpc/flipt/auth/auth.proto @@ -78,6 +78,7 @@ message CreateTokenRequest { string description = 2; google.protobuf.Timestamp expires_at = 3; string namespace_key = 4; + map metadata = 7; } message CreateTokenResponse { diff --git a/rpc/flipt/request.go b/rpc/flipt/request.go index 1c7c911a4c..1a00da9af5 100644 --- a/rpc/flipt/request.go +++ b/rpc/flipt/request.go @@ -51,7 +51,9 @@ type Request struct { func WithNamespace(ns string) func(*Request) { return func(r *Request) { - r.Namespace = ns + if ns != "" { + r.Namespace = ns + } } } @@ -69,9 +71,10 @@ func WithSubject(s Subject) func(*Request) { func NewRequest(r Resource, a Action, opts ...func(*Request)) Request { req := Request{ - Resource: r, - Action: a, - Status: StatusSuccess, + Resource: r, + Action: a, + Status: StatusSuccess, + Namespace: DefaultNamespace, } for _, opt := range opts { @@ -107,7 +110,7 @@ func (req *UpdateNamespaceRequest) Request() Request { } func (req *DeleteNamespaceRequest) Request() Request { - return NewRequest(ResourceFlag, ActionDelete, WithNamespace(req.Key)) + return NewRequest(ResourceNamespace, ActionDelete, WithNamespace(req.Key)) } // Flags