mirror of
https://github.com/openshift/openshift-docs.git
synced 2026-02-05 12:46:18 +01:00
183 lines
5.9 KiB
Plaintext
183 lines
5.9 KiB
Plaintext
// 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.
|
|
====
|
|
|
|
.Prerequisites
|
|
|
|
* {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"]
|
|
----
|