diff --git a/docs/cspell.json b/docs/cspell.json index 2bedf8ff506e..fa8bd97efc41 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -910,6 +910,7 @@ "teleportdevname", "teleportdevprotocol", "teleporters", + "teleportgithubconnector", "teleportinfra", "teleportopensshserverv", "teleportproxy", diff --git a/docs/pages/admin-guides/infrastructure-as-code/teleport-operator.mdx b/docs/pages/admin-guides/infrastructure-as-code/teleport-operator.mdx index 118d00180741..ca6fe1ca2148 100644 --- a/docs/pages/admin-guides/infrastructure-as-code/teleport-operator.mdx +++ b/docs/pages/admin-guides/infrastructure-as-code/teleport-operator.mdx @@ -72,6 +72,17 @@ finalizer or remove the ignore annotation. Possible values are `"true"` or `"false"` (those are strings, as Booleans are not valid label values in Kubernetes). +### Look up values from secrets + +Some Teleport resources might contain sensitive values. Select CR fields can reference an existing +Kubernetes secret and the operator will retrieve the value from the secret when reconciling. + +Even when you store sensitive values out of CRs, the CRs must still be considered as critical as +the Kubernetes secrets themselves. Many CRs configure Teleport RBAC. Someone with CR editing permissions can become a +Teleport administrator and retrieve the sensitive values from Teleport. + +See [the dedicated guide](./teleport-operator/secret-lookup.mdx) for more details. + ### Troubleshooting (!docs/pages/includes/diagnostics/kubernetes-operator-troubleshooting.mdx!) diff --git a/docs/pages/admin-guides/infrastructure-as-code/teleport-operator/secret-lookup.mdx b/docs/pages/admin-guides/infrastructure-as-code/teleport-operator/secret-lookup.mdx new file mode 100644 index 000000000000..9cf816d8a015 --- /dev/null +++ b/docs/pages/admin-guides/infrastructure-as-code/teleport-operator/secret-lookup.mdx @@ -0,0 +1,137 @@ +--- +title: Looking up values from secrets +description: How to store sensitive values in a Kubernetes Secret and have the operator look them up. +--- + +This guide describes how to store sensitive information in Kubernetes Secrets instead +of the Teleport Kubernetes operator CRs. + +## How it works + +Some Teleport resources might contain sensitive values. Select CR fields can reference an existing +Kubernetes secret and the operator will retrieve the value from the secret when reconciling. + +Currently only the GithubConnector and OIDCConnector `client_secret` field support secret lookup. + +## Prerequisites + +To follow this guide you need: + +- A running Teleport cluster +- [A functional Teleport Kubernetes operator setup](../teleport-operator.mdx#setting-up-the-operator) +- Kubernetes rights to edit CRs and Secrets in the operator namespace +- `kubectl` installed locally and configured for your Kubernetes cluster +- A working GitHub or OIDC connector you want to manage with the operator +- `tctl` and `tsh` installed and logged in the Teleport cluster + +## Important Considerations + +Even when you store sensitive values out of CRs, the CRs must still be considered as critical as +the Kubernetes secrets themselves. Many CRs configure Teleport RBAC. Someone with permissions to edit CRs can become a +Teleport administrator and retrieve the sensitive values from Teleport. + +The secret lookup feature has two limitations you must take into account before configuring it: +- for performance reasons, the secret is not watched. A secret content change is not immediately reflected on + the resource. To force the operator to use the new secret value, you must trigger a reconciliation by editing the CR, + restarting the operator, or waiting for the next full sync (every 12 hours). +- for security reasons, the operator doesn't allow lookup from arbitrary secrets. The secret must be annotated with + `resources.teleport.dev/allow-lookup-from-cr`. Possible values are `*`, or a comma-separated list of CR names. + +## Step 1/3. Create a Kubernetes Secret containing the sensitive value + +For this guide, the sensitive value we want to store is the GitHub connector client secret. + +Create the following `secret.yaml` manifest: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: teleport-github-connector + annotations: + # This annotation allows any CR to look up this secret + resources.teleport.dev/allow-lookup-from-cr: "*" +# We use stringData instead of data for the sake of simplicity, both are OK +stringData: + githubSecret: my-github-secret-value +``` + +If is your Teleport Kubernetes operator namespace, apply the manifest using `kubectl`: + +```code +$ kubectl apply -n -f secret.yaml +secret/teleport-github-connector created +``` + +## Step 2/3. Create a custom resource referencing the secret + +Create the following `github-connector.yaml` manifest: + +```yaml +apiVersion: resources.teleport.dev/v3 +kind: TeleportGithubConnector +metadata: + name: github +spec: + # This value will be looked up from the secret. `teleport-github-connector` is the secret name and `githubSecret` is the secret key. + client_secret: "secret://teleport-github-connector/githubSecret" + # Replace all the values below by the ones to work with your github account + client_id: my-client-id + display: Github + redirect_url: "my value" + teams_to_roles: + - organization: ORG-NAME + roles: + - access + team: team-name +``` + +Apply the manifest in the same namespace as the operator and the secret: + +```code +$ kubectl apply -n -f github-connector.yaml +teleportgithubconnector.resources.teleport.dev/github created +``` + +## Step 3/3. Validate the resource was created + +The operator indicates if the reconciliation worked on the CR `status` field. +Run the following command to know if it worked: + +```code +$ kubectl get -n -n teleportgithubconnector github -o yaml + +apiVersion: resources.teleport.dev/v3 +kind: TeleportGithubConnector +# [...] +status: + conditions: + - lastTransitionTime: "2022-07-25T16:15:52Z" + message: Teleport resource has the Kubernetes origin label. + reason: OriginLabelMatching + status: "True" + type: TeleportResourceOwned + - lastTransitionTime: "2022-07-25T17:08:58Z" + message: 'Teleport Resource was successfully reconciled, no error was returned by Teleport.' + reason: NoError + status: "True" + type: SuccessfullyReconciled +``` + +If everything worked, all condition statuses should be `True`. Of some status is `False`, the message and the reason +will give you more information about what failed. + +Finally, validate the resource has been properly created in Teleport: +```code +$ tctl get github +version: v3 +kind: github +metadata: + name: github +spec: + client_secret: "my-github-secret-value" + # ... +``` + +You should see that the content of `spec.client_secret` has been replaced by the secret's content. + diff --git a/docs/pages/reference/operator-resources/resources.teleport.dev_githubconnectors.mdx b/docs/pages/reference/operator-resources/resources.teleport.dev_githubconnectors.mdx index 66132ae92133..0b95b075c0a7 100644 --- a/docs/pages/reference/operator-resources/resources.teleport.dev_githubconnectors.mdx +++ b/docs/pages/reference/operator-resources/resources.teleport.dev_githubconnectors.mdx @@ -29,7 +29,7 @@ resource, which you can apply after installing the Teleport Kubernetes operator. |api_endpoint_url|string|APIEndpointURL is the URL of the API endpoint of the Github instance this connector is for.| |client_id|string|ClientID is the Github OAuth app client ID.| |client_redirect_settings|[object](#specclient_redirect_settings)|ClientRedirectSettings defines which client redirect URLs are allowed for non-browser SSO logins other than the standard localhost ones.| -|client_secret|string|ClientSecret is the Github OAuth app client secret.| +|client_secret|string|ClientSecret is the Github OAuth app client secret. This field supports secret lookup. See the operator documentation for more details.| |display|string|Display is the connector display name.| |endpoint_url|string|EndpointURL is the URL of the GitHub instance this connector is for.| |redirect_url|string|RedirectURL is the authorization callback URL.| diff --git a/docs/pages/reference/operator-resources/resources.teleport.dev_oidcconnectors.mdx b/docs/pages/reference/operator-resources/resources.teleport.dev_oidcconnectors.mdx index 1b650cbd95b4..891d87a06763 100644 --- a/docs/pages/reference/operator-resources/resources.teleport.dev_oidcconnectors.mdx +++ b/docs/pages/reference/operator-resources/resources.teleport.dev_oidcconnectors.mdx @@ -31,7 +31,7 @@ resource, which you can apply after installing the Teleport Kubernetes operator. |claims_to_roles|[][object](#specclaims_to_roles-items)|ClaimsToRoles specifies a dynamic mapping from claims to roles.| |client_id|string|ClientID is the id of the authentication client (Teleport Auth server).| |client_redirect_settings|[object](#specclient_redirect_settings)|ClientRedirectSettings defines which client redirect URLs are allowed for non-browser SSO logins other than the standard localhost ones.| -|client_secret|string|ClientSecret is used to authenticate the client.| +|client_secret|string|ClientSecret is used to authenticate the client. This field supports secret lookup. See the operator documentation for more details.| |display|string|Display is the friendly name for this provider.| |google_admin_email|string|GoogleAdminEmail is the email of a google admin to impersonate.| |google_service_account|string|GoogleServiceAccount is a string containing google service account credentials.| diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_githubconnectors.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_githubconnectors.yaml index a92d5dbf5eff..be8404b633bc 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_githubconnectors.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_githubconnectors.yaml @@ -64,7 +64,9 @@ spec: type: array type: object client_secret: - description: ClientSecret is the Github OAuth app client secret. + description: ClientSecret is the Github OAuth app client secret. This + field supports secret lookup. See the operator documentation for + more details. type: string display: description: Display is the connector display name. diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_oidcconnectors.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_oidcconnectors.yaml index b801cf6db84f..10bbfed040a5 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_oidcconnectors.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_oidcconnectors.yaml @@ -89,7 +89,9 @@ spec: type: array type: object client_secret: - description: ClientSecret is used to authenticate the client. + description: ClientSecret is used to authenticate the client. This + field supports secret lookup. See the operator documentation for + more details. type: string display: description: Display is the friendly name for this provider. diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml index 666c2ae7bed9..90bf13b69cc5 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml @@ -6,6 +6,7 @@ metadata: name: {{ include "teleport-cluster.operator.fullname" . }} namespace: {{ .Release.Namespace }} rules: + # Rights to manage the Teleport CRs - apiGroups: - "resources.teleport.dev" resources: @@ -41,6 +42,7 @@ rules: - patch - update - watch + # Used to perform leader election when running with multiple replicas - apiGroups: - "coordination.k8s.io" resources: @@ -49,11 +51,19 @@ rules: - create - get - update + # Ability to emit reconciliation events - apiGroups: - "" resources: - events verbs: - create + # Ability to lookup sensitive values from secrets rather than CRs + - apiGroups: + - "" + resources: + - "secrets" + verbs: + - "get" {{- end -}} {{- end -}} diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/tests/role_test.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/tests/role_test.yaml index a0dce6550486..3cbb29023dfa 100644 --- a/examples/chart/teleport-cluster/charts/teleport-operator/tests/role_test.yaml +++ b/examples/chart/teleport-cluster/charts/teleport-operator/tests/role_test.yaml @@ -41,3 +41,12 @@ tests: kind: Role apiVersion: rbac.authorization.k8s.io/v1 name: RELEASE-NAME-operator + + - it: grants access to secret in the namespace + asserts: + - contains: + path: rules + content: + apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] \ No newline at end of file diff --git a/integrations/operator/config/crd/bases/resources.teleport.dev_githubconnectors.yaml b/integrations/operator/config/crd/bases/resources.teleport.dev_githubconnectors.yaml index a92d5dbf5eff..be8404b633bc 100644 --- a/integrations/operator/config/crd/bases/resources.teleport.dev_githubconnectors.yaml +++ b/integrations/operator/config/crd/bases/resources.teleport.dev_githubconnectors.yaml @@ -64,7 +64,9 @@ spec: type: array type: object client_secret: - description: ClientSecret is the Github OAuth app client secret. + description: ClientSecret is the Github OAuth app client secret. This + field supports secret lookup. See the operator documentation for + more details. type: string display: description: Display is the connector display name. diff --git a/integrations/operator/config/crd/bases/resources.teleport.dev_oidcconnectors.yaml b/integrations/operator/config/crd/bases/resources.teleport.dev_oidcconnectors.yaml index b801cf6db84f..10bbfed040a5 100644 --- a/integrations/operator/config/crd/bases/resources.teleport.dev_oidcconnectors.yaml +++ b/integrations/operator/config/crd/bases/resources.teleport.dev_oidcconnectors.yaml @@ -89,7 +89,9 @@ spec: type: array type: object client_secret: - description: ClientSecret is used to authenticate the client. + description: ClientSecret is used to authenticate the client. This + field supports secret lookup. See the operator documentation for + more details. type: string display: description: Display is the friendly name for this provider. diff --git a/integrations/operator/controllers/resources/github_connector_controller.go b/integrations/operator/controllers/resources/github_connector_controller.go index cb400d3ce4c7..f0d2884b6feb 100644 --- a/integrations/operator/controllers/resources/github_connector_controller.go +++ b/integrations/operator/controllers/resources/github_connector_controller.go @@ -29,11 +29,13 @@ import ( resourcesv3 "github.com/gravitational/teleport/integrations/operator/apis/resources/v3" "github.com/gravitational/teleport/integrations/operator/controllers" "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers" + "github.com/gravitational/teleport/integrations/operator/controllers/resources/secretlookup" ) // githubConnectorClient implements TeleportResourceClient and offers CRUD methods needed to reconcile github_connectors type githubConnectorClient struct { teleportClient *client.Client + kubeClient kclient.Client } // Get gets the Teleport github_connector of a given name @@ -59,10 +61,23 @@ func (r githubConnectorClient) Delete(ctx context.Context, name string) error { return trace.Wrap(r.teleportClient.DeleteGithubConnector(ctx, name)) } +func (r githubConnectorClient) Mutate(ctx context.Context, new, _ types.GithubConnector, crKey kclient.ObjectKey) error { + secret := new.GetClientSecret() + if secretlookup.IsNeeded(secret) { + resolvedSecret, err := secretlookup.Try(ctx, r.kubeClient, crKey.Name, crKey.Namespace, secret) + if err != nil { + return trace.Wrap(err) + } + new.SetClientSecret(resolvedSecret) + } + return nil +} + // NewGithubConnectorReconciler instantiates a new Kubernetes controller reconciling github_connector resources func NewGithubConnectorReconciler(client kclient.Client, tClient *client.Client) (controllers.Reconciler, error) { githubClient := &githubConnectorClient{ teleportClient: tClient, + kubeClient: client, } resourceReconciler, err := reconcilers.NewTeleportResourceWithoutLabelsReconciler[types.GithubConnector, *resourcesv3.TeleportGithubConnector]( diff --git a/integrations/operator/controllers/resources/github_connector_controller_test.go b/integrations/operator/controllers/resources/github_connector_controller_test.go index 321f61cfae73..2bc074a1825f 100644 --- a/integrations/operator/controllers/resources/github_connector_controller_test.go +++ b/integrations/operator/controllers/resources/github_connector_controller_test.go @@ -24,12 +24,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/gravitational/teleport/api/types" resourcesv3 "github.com/gravitational/teleport/integrations/operator/apis/resources/v3" "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers" + "github.com/gravitational/teleport/integrations/operator/controllers/resources/secretlookup" "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib" ) @@ -136,3 +139,51 @@ func TestGithubConnectorUpdate(t *testing.T) { test := &githubTestingPrimitives{} testlib.ResourceUpdateTest[types.GithubConnector, *resourcesv3.TeleportGithubConnector](t, test) } + +func TestGithubConnectorSecretLookup(t *testing.T) { + test := &githubTestingPrimitives{} + setup := testlib.SetupTestEnv(t) + test.Init(setup) + ctx := context.Background() + + crName := validRandomResourceName("github") + secretName := validRandomResourceName("github-secret") + secretKey := "client-secret" + secretValue := validRandomResourceName("secret-value") + + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: setup.Namespace.Name, + Annotations: map[string]string{ + secretlookup.AllowLookupAnnotation: crName, + }, + }, + StringData: map[string]string{ + secretKey: secretValue, + }, + Type: v1.SecretTypeOpaque, + } + kubeClient := setup.K8sClient + require.NoError(t, kubeClient.Create(ctx, secret)) + + github := &resourcesv3.TeleportGithubConnector{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + Namespace: setup.Namespace.Name, + }, + Spec: resourcesv3.TeleportGithubConnectorSpec(githubSpec), + } + + github.Spec.ClientSecret = "secret://" + secretName + "/" + secretKey + + require.NoError(t, kubeClient.Create(ctx, github)) + + testlib.FastEventually(t, func() bool { + gh, err := test.GetTeleportResource(ctx, crName) + if err != nil { + return false + } + return gh.GetClientSecret() == secretValue + }) +} diff --git a/integrations/operator/controllers/resources/oidc_connector_controller.go b/integrations/operator/controllers/resources/oidc_connector_controller.go index d6f5cc340184..360e4c572ff7 100644 --- a/integrations/operator/controllers/resources/oidc_connector_controller.go +++ b/integrations/operator/controllers/resources/oidc_connector_controller.go @@ -29,11 +29,13 @@ import ( resourcesv3 "github.com/gravitational/teleport/integrations/operator/apis/resources/v3" "github.com/gravitational/teleport/integrations/operator/controllers" "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers" + "github.com/gravitational/teleport/integrations/operator/controllers/resources/secretlookup" ) // oidcConnectorClient implements TeleportResourceClient and offers CRUD methods needed to reconcile oidc_connectors type oidcConnectorClient struct { teleportClient *client.Client + kubeClient kclient.Client } // Get gets the Teleport oidc_connector of a given name @@ -59,10 +61,23 @@ func (r oidcConnectorClient) Delete(ctx context.Context, name string) error { return trace.Wrap(r.teleportClient.DeleteOIDCConnector(ctx, name)) } +func (r oidcConnectorClient) Mutate(ctx context.Context, new, _ types.OIDCConnector, crKey kclient.ObjectKey) error { + secret := new.GetClientSecret() + if secretlookup.IsNeeded(secret) { + resolvedSecret, err := secretlookup.Try(ctx, r.kubeClient, crKey.Name, crKey.Namespace, secret) + if err != nil { + return trace.Wrap(err) + } + new.SetClientSecret(resolvedSecret) + } + return nil +} + // NewOIDCConnectorReconciler instantiates a new Kubernetes controller reconciling oidc_connector resources func NewOIDCConnectorReconciler(client kclient.Client, tClient *client.Client) (controllers.Reconciler, error) { oidcClient := &oidcConnectorClient{ teleportClient: tClient, + kubeClient: client, } resourceReconciler, err := reconcilers.NewTeleportResourceWithoutLabelsReconciler[types.OIDCConnector, *resourcesv3.TeleportOIDCConnector]( diff --git a/integrations/operator/controllers/resources/oidc_connector_controller_test.go b/integrations/operator/controllers/resources/oidc_connector_controller_test.go index 5b3984ce7b6e..35228bc8188f 100644 --- a/integrations/operator/controllers/resources/oidc_connector_controller_test.go +++ b/integrations/operator/controllers/resources/oidc_connector_controller_test.go @@ -24,12 +24,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/gravitational/teleport/api/types" resourcesv3 "github.com/gravitational/teleport/integrations/operator/apis/resources/v3" "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers" + "github.com/gravitational/teleport/integrations/operator/controllers/resources/secretlookup" "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib" ) @@ -135,3 +138,51 @@ func TestOIDCConnectorUpdate(t *testing.T) { test := &oidcTestingPrimitives{} testlib.ResourceUpdateTest[types.OIDCConnector, *resourcesv3.TeleportOIDCConnector](t, test) } + +func TestOIDCConnectorSecretLookup(t *testing.T) { + test := &oidcTestingPrimitives{} + setup := testlib.SetupTestEnv(t) + test.Init(setup) + ctx := context.Background() + + crName := validRandomResourceName("oidc") + secretName := validRandomResourceName("oidc-secret") + secretKey := "client-secret" + secretValue := validRandomResourceName("secret-value") + + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: setup.Namespace.Name, + Annotations: map[string]string{ + secretlookup.AllowLookupAnnotation: crName, + }, + }, + StringData: map[string]string{ + secretKey: secretValue, + }, + Type: v1.SecretTypeOpaque, + } + kubeClient := setup.K8sClient + require.NoError(t, kubeClient.Create(ctx, secret)) + + oidc := &resourcesv3.TeleportOIDCConnector{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + Namespace: setup.Namespace.Name, + }, + Spec: resourcesv3.TeleportOIDCConnectorSpec(oidcSpec), + } + + oidc.Spec.ClientSecret = "secret://" + secretName + "/" + secretKey + + require.NoError(t, kubeClient.Create(ctx, oidc)) + + testlib.FastEventually(t, func() bool { + oidc, err := test.GetTeleportResource(ctx, crName) + if err != nil { + return false + } + return oidc.GetClientSecret() == secretValue + }) +} diff --git a/integrations/operator/controllers/resources/secretlookup/secretlookup.go b/integrations/operator/controllers/resources/secretlookup/secretlookup.go index 68d25b5f0e24..a156b9cc47c8 100644 --- a/integrations/operator/controllers/resources/secretlookup/secretlookup.go +++ b/integrations/operator/controllers/resources/secretlookup/secretlookup.go @@ -35,9 +35,9 @@ const ( secretScheme = "secret" secretPrefix = secretScheme + "://" - // AllowInclusionAnnotation is the annotation a secret must wear for the operator to allow looking up its content + // AllowLookupAnnotation is the annotation a secret must wear for the operator to allow looking up its content // when reconciling a resource. Its value is either a comma-separated list of allowed resources, or a '*'. - AllowInclusionAnnotation = "resources.teleport.dev/allow-inclusion-from-cr" + AllowLookupAnnotation = "resources.teleport.dev/allow-lookup-from-cr" ) // IsNeeded checks if a string starts with "secret://" and needs a secret lookup. @@ -89,13 +89,13 @@ func Try(ctx context.Context, clt kclient.Client, name, namespace, uri string) ( } // isInclusionAllowed checks if the secret allows inclusion from the CR. -// The secret must wear the AllowInclusionAnnotation and the annotation must either +// The secret must wear the AllowLookupAnnotation and the annotation must either // explicitly allow the resource, or allow any resource ("*"). func isInclusionAllowed(secret *corev1.Secret, name string) error { secretName := secret.Name - annotation, ok := secret.Annotations[AllowInclusionAnnotation] + annotation, ok := secret.Annotations[AllowLookupAnnotation] if !ok { - return trace.BadParameter("secret %q doesn't have the %q annotation", secretName, AllowInclusionAnnotation) + return trace.BadParameter("secret %q doesn't have the %q annotation", secretName, AllowLookupAnnotation) } if annotation == types.Wildcard { return nil @@ -106,5 +106,5 @@ func isInclusionAllowed(secret *corev1.Secret, name string) error { return nil } } - return trace.AccessDenied("secret %q have the annotation %q but it does not contain %q", secretName, AllowInclusionAnnotation, name) + return trace.AccessDenied("secret %q have the annotation %q but it does not contain %q", secretName, AllowLookupAnnotation, name) } diff --git a/integrations/operator/controllers/resources/secretlookup/secretlookup_test.go b/integrations/operator/controllers/resources/secretlookup/secretlookup_test.go index ec01f14b465d..64fea92f94b1 100644 --- a/integrations/operator/controllers/resources/secretlookup/secretlookup_test.go +++ b/integrations/operator/controllers/resources/secretlookup/secretlookup_test.go @@ -53,7 +53,7 @@ func TestLookupSecret(t *testing.T) { Type: v1.SecretTypeOpaque, } } - okAnnotations := map[string]string{AllowInclusionAnnotation: strings.Join([]string{"other-cr-name", crName}, ", ")} + okAnnotations := map[string]string{AllowLookupAnnotation: strings.Join([]string{"other-cr-name", crName}, ", ")} okSecret := secretWithAnnotations(okAnnotations) // Test setup: defining test cases @@ -107,7 +107,7 @@ func TestLookupSecret(t *testing.T) { { name: "secret annotations don't allow inclusion", input: fmt.Sprintf("secret://%s/%s", secretName, keyName), - secrets: v1.SecretList{Items: []v1.Secret{secretWithAnnotations(map[string]string{AllowInclusionAnnotation: "not-the-right-cr"})}}, + secrets: v1.SecretList{Items: []v1.Secret{secretWithAnnotations(map[string]string{AllowLookupAnnotation: "not-the-right-cr"})}}, assertErr: func(t require.TestingT, err error, i ...interface{}) { require.ErrorContains(t, err, "does not contain") }, @@ -192,7 +192,7 @@ func Test_isInclusionAllowed(t *testing.T) { secret: v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - AllowInclusionAnnotation: "", + AllowLookupAnnotation: "", }, }, }, @@ -203,7 +203,7 @@ func Test_isInclusionAllowed(t *testing.T) { secret: v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - AllowInclusionAnnotation: "foo, bar", + AllowLookupAnnotation: "foo, bar", }, }, }, @@ -214,7 +214,7 @@ func Test_isInclusionAllowed(t *testing.T) { secret: v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - AllowInclusionAnnotation: crName, + AllowLookupAnnotation: crName, }, }, }, @@ -225,7 +225,7 @@ func Test_isInclusionAllowed(t *testing.T) { secret: v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - AllowInclusionAnnotation: strings.Join([]string{"foo", "bar", crName}, ", "), + AllowLookupAnnotation: strings.Join([]string{"foo", "bar", crName}, ", "), }, }, }, @@ -236,7 +236,7 @@ func Test_isInclusionAllowed(t *testing.T) { secret: v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ - AllowInclusionAnnotation: "*", + AllowLookupAnnotation: "*", }, }, }, diff --git a/integrations/operator/crdgen/additional_doc.go b/integrations/operator/crdgen/additional_doc.go new file mode 100644 index 000000000000..2458af6779bb --- /dev/null +++ b/integrations/operator/crdgen/additional_doc.go @@ -0,0 +1,32 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package crdgen + +const supportsSecretLookupDescription = `This field supports secret lookup. See the operator documentation for more details.` + +// additionalDescription contains additional description we want to add to select fields. +// This is used to document operator-specific behaviors, such as the secret lookup. +var additionalDescription = map[string]map[string]string{ + "GithubConnectorSpecV3": { + "ClientSecret": supportsSecretLookupDescription, + }, + "OIDCConnectorSpecV3": { + "ClientSecret": supportsSecretLookupDescription, + }, +} diff --git a/integrations/operator/crdgen/schemagen.go b/integrations/operator/crdgen/schemagen.go index 8f4c3c7795ec..9862780b66d2 100644 --- a/integrations/operator/crdgen/schemagen.go +++ b/integrations/operator/crdgen/schemagen.go @@ -280,6 +280,7 @@ func (generator *SchemaGenerator) traverseInner(message *Message) (*Schema, erro generator.memo[name] = schema for _, field := range message.Fields { + // Skip the ignored fields if _, ok := ignoredFields[message.Name()][field.Name()]; ok { continue } @@ -296,11 +297,17 @@ func (generator *SchemaGenerator) traverseInner(message *Message) (*Schema, erro continue } - var err error - schema.Properties[jsonName], err = generator.prop(field) + prop, err := generator.prop(field) if err != nil { return nil, trace.Wrap(err) } + + // If the field has custom additional description, we append it. + if desc, ok := additionalDescription[message.Name()][field.Name()]; ok { + prop.Description = prop.Description + " " + desc + } + + schema.Properties[jsonName] = prop } schema.built = true diff --git a/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_githubconnectors.yaml b/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_githubconnectors.yaml index a92d5dbf5eff..be8404b633bc 100644 --- a/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_githubconnectors.yaml +++ b/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_githubconnectors.yaml @@ -64,7 +64,9 @@ spec: type: array type: object client_secret: - description: ClientSecret is the Github OAuth app client secret. + description: ClientSecret is the Github OAuth app client secret. This + field supports secret lookup. See the operator documentation for + more details. type: string display: description: Display is the connector display name. diff --git a/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_oidcconnectors.yaml b/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_oidcconnectors.yaml index b801cf6db84f..10bbfed040a5 100644 --- a/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_oidcconnectors.yaml +++ b/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_oidcconnectors.yaml @@ -89,7 +89,9 @@ spec: type: array type: object client_secret: - description: ClientSecret is used to authenticate the client. + description: ClientSecret is used to authenticate the client. This + field supports secret lookup. See the operator documentation for + more details. type: string display: description: Display is the friendly name for this provider.