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.