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-gcp-enabling.adoc

183 lines
5.9 KiB
Plaintext
Raw Permalink Normal View History

2024-09-05 11:41:07 -06:00
// Module included in the following assemblies:
//
// * operators/operator_sdk/osdk-token-auth/osdk-cco-gcp.adoc
:_mod-docs-content-type: PROCEDURE
[id="osdk-cco-gcp-enabling_{context}"]
= Enabling Operators to support CCO-based workflows with {gcp-wid-short}
As an Operator author designing your project to run on Operator Lifecycle Manager (OLM), you can enable your Operator to authenticate against {gcp-wid-first} on {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.
====
2026-01-14 16:19:30 -05:00
.Prerequisites
2024-09-05 11:41:07 -06:00
* {product-title} 4.17 or later
* Cluster in *{gcp-wid-short} / Federated Identity* mode
* OLM-based Operator project
.Procedure
. Update your Operator project's `ClusterServiceVersion` (CSV) object:
.. Ensure Operator deployment in the CSV has the following `volumeMounts` and `volumes` fields so that the Operator can assume the role with web identity:
+
.Example `volumeMounts` and `volumes` fields
[%collapsible]
====
[source,yaml]
----
# ...
volumeMounts:
- name: bound-sa-token
mountPath: /var/run/secrets/openshift/serviceaccount
readOnly: true
volumes:
# This service account token can be used to provide identity outside the cluster.
- name: bound-sa-token
projected:
sources:
- serviceAccountToken:
path: token
audience: openshift
----
====
.. 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 {gcp-wid-short}:
+
[source,yaml]
----
# ...
metadata:
annotations:
features.operators.openshift.io/token-auth-gcp: "true"
----
. Update your Operator project code:
.. Get the `audience` and the `serviceAccountEmail` values from the environment variables set on the pod by the subscription config:
+
[source,go]
----
// Get ENV var
audience := os.Getenv("AUDIENCE")
serviceAccountEmail := os.Getenv("SERVICE_ACCOUNT_EMAIL")
gcpIdentityTokenFile := "/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 {gcp-wid-short} variables 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.GCPProviderSpec.Audience = audience
credReqTemplate.Spec.GCPProviderSpec.ServiceAccountEmail = serviceAccountEmail
credReqTemplate.CloudTokenPath = gcpIdentityTokenFile
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
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 `service_account.json` field from the secret and use it to authenticate your {gcp-short} client:
+
[source,go]
----
service_account_json := secret.StringData["service_account.json"]
----