mirror of
https://github.com/openshift/openshift-docs.git
synced 2026-02-05 12:46:18 +01:00
155 lines
5.3 KiB
Plaintext
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. |