1
0
mirror of https://github.com/openshift/openshift-docs.git synced 2026-02-05 12:46:18 +01:00
Files
openshift-docs/modules/osdk-cco-azure-enabling.adoc
2024-09-26 05:33:53 +00:00

155 lines
5.3 KiB
Plaintext

// Module included in the following assemblies:
//
// * operators/operator_sdk/token_auth/osdk-cco-azure.adoc
:_mod-docs-content-type: PROCEDURE
[id="osdk-cco-azure-enabling_{context}"]
= Enabling Operators to support CCO-based workflows with {entra-first}
As an Operator author designing your project to run on Operator Lifecycle Manager (OLM), you can enable your Operator to authenticate against {entra-first}-enabled {product-title} clusters by customizing your project to support the Cloud Credential Operator (CCO).
With this method, the Operator is responsible for and requires RBAC permissions for creating the `CredentialsRequest` object and reading the resulting `Secret` object.
[NOTE]
====
By default, pods related to the Operator deployment mount a `serviceAccountToken` volume so that the service account token can be referenced in the resulting `Secret` object.
====
.Prerequisities
* {product-title} 4.14 or later
* Cluster in {entra-short} mode
* OLM-based Operator project
.Procedure
. Update your Operator project's `ClusterServiceVersion` (CSV) object:
.. Ensure your Operator has RBAC permission to create `CredentialsRequests` objects:
+
.Example `clusterPermissions` list
[%collapsible]
====
[source,yaml]
----
# ...
install:
spec:
clusterPermissions:
- rules:
- apiGroups:
- "cloudcredential.openshift.io"
resources:
- credentialsrequests
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
----
====
.. Add the following annotation to claim support for this method of CCO-based workflow with {entra-short}:
+
[source,yaml]
----
# ...
metadata:
annotations:
features.operators.openshift.io/token-auth-azure: "true"
----
. Update your Operator project code:
.. Get the client ID, tenant ID, and subscription ID from the environment variables set on the pod by the `Subscription` object. For example:
+
[source,go]
----
// Get ENV var
clientID := os.Getenv("CLIENTID")
tenantID := os.Getenv("TENANTID")
subscriptionID := os.Getenv("SUBSCRIPTIONID")
azureFederatedTokenFile := "/var/run/secrets/openshift/serviceaccount/token"
----
.. Ensure you have a `CredentialsRequest` object ready to be patched and applied.
+
[NOTE]
====
Adding a `CredentialsRequest` object to the Operator bundle is not currently supported.
====
.. Add the Azure credentials information and web identity token path to the credentials request and apply it during Operator initialization:
+
.Example applying `CredentialsRequest` object during Operator initialization
[%collapsible]
====
[source,go]
----
// apply CredentialsRequest on install
credReqTemplate.Spec.AzureProviderSpec.AzureClientID = clientID
credReqTemplate.Spec.AzureProviderSpec.AzureTenantID = tenantID
credReqTemplate.Spec.AzureProviderSpec.AzureRegion = "centralus"
credReqTemplate.Spec.AzureProviderSpec.AzureSubscriptionID = subscriptionID
credReqTemplate.CloudTokenPath = azureFederatedTokenFile
c := mgr.GetClient()
if err := c.Create(context.TODO(), credReq); err != nil {
if !errors.IsAlreadyExists(err) {
setupLog.Error(err, "unable to create CredRequest")
os.Exit(1)
}
}
----
====
.. Ensure your Operator can wait for a `Secret` object to show up from the CCO, as shown in the following example, which is called along with the other items you are reconciling in your Operator:
+
.Example wait for `Secret` object
[%collapsible]
====
[source,go]
----
// WaitForSecret is a function that takes a Kubernetes client, a namespace, and a v1 "k8s.io/api/core/v1" name as arguments
// It waits until the secret object with the given name exists in the given namespace
// It returns the secret object or an error if the timeout is exceeded
func WaitForSecret(client kubernetes.Interface, namespace, name string) (*v1.Secret, error) {
// set a timeout of 10 minutes
timeout := time.After(10 * time.Minute) <1>
// set a polling interval of 10 seconds
ticker := time.NewTicker(10 * time.Second)
// loop until the timeout or the secret is found
for {
select {
case <-timeout:
// timeout is exceeded, return an error
return nil, fmt.Errorf("timed out waiting for secret %s in namespace %s", name, namespace)
// add to this error with a pointer to instructions for following a manual path to a Secret that will work on STS
case <-ticker.C:
// polling interval is reached, try to get the secret
secret, err := client.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
// secret does not exist yet, continue waiting
continue
} else {
// some other error occurred, return it
return nil, err
}
} else {
// secret is found, return it
return secret, nil
}
}
}
}
----
<1> The `timeout` value is based on an estimate of how fast the CCO might detect an added `CredentialsRequest` object and generate a `Secret` object. You might consider lowering the time or creating custom feedback for cluster administrators that could be wondering why the Operator is not yet accessing the cloud resources.
====
.. Read the secret created by the CCO from the `CredentialsRequest` object to authenticate with Azure and receive the necessary credentials.