diff --git a/docs/book/src/topics/rosa/creating-a-cluster.md b/docs/book/src/topics/rosa/creating-a-cluster.md index b28c19cbad..6b394d9569 100644 --- a/docs/book/src/topics/rosa/creating-a-cluster.md +++ b/docs/book/src/topics/rosa/creating-a-cluster.md @@ -1,30 +1,18 @@ # Creating a ROSA cluster ## Permissions -CAPA controller requires an API token in order to be able to provision ROSA clusters: +CAPA controller requires service account credentials to be able to provision ROSA clusters: +1. Visit [https://console.redhat.com/iam/service-accounts](https://console.redhat.com/iam/service-accounts) and create a new service account. -1. Visit [https://console.redhat.com/openshift/token](https://console.redhat.com/openshift/token) to retrieve your API authentication token - -1. Create a credentials secret within the target namespace with the token to be referenced later by `ROSAControlePlane` +1. Create a new kubernetes secret with the service account credentials to be referenced later by `ROSAControlPlane` ```shell kubectl create secret generic rosa-creds-secret \ - --from-literal=ocmToken='eyJhbGciOiJIUzI1NiIsI....' \ + --from-literal=ocmClientId='....' \ + --from-literal=ocmClientSecret='eyJhbGciOiJIUzI1NiIsI....' \ --from-literal=ocmApiUrl='https://api.openshift.com' ``` - Alternatively, you can edit CAPA controller deployment to provide the credentials: - ```shell - kubectl edit deployment -n capa-system capa-controller-manager - ``` - - and add the following environment variables to the manager container: - ```yaml - env: - - name: OCM_TOKEN - value: "" - - name: OCM_API_URL - value: "https://api.openshift.com" # or https://api.stage.openshift.com - ``` + Note: to consume the secret without the need to reference it from your `ROSAControlPlane`, name your secret as `default-rosa-creds-secret`. ## Prerequisites diff --git a/pkg/rosa/client.go b/pkg/rosa/client.go index 36c9ae333b..52ebca1530 100644 --- a/pkg/rosa/client.go +++ b/pkg/rosa/client.go @@ -4,51 +4,76 @@ package rosa import ( "context" "fmt" - "os" sdk "github.com/openshift-online/ocm-sdk-go" ocmcfg "github.com/openshift/rosa/pkg/config" "github.com/openshift/rosa/pkg/ocm" "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" ) const ( - ocmTokenKey = "ocmToken" - ocmAPIURLKey = "ocmApiUrl" + ocmTokenKey = "ocmToken" + ocmAPIURLKey = "ocmApiUrl" + ocmClientIdKey = "ocmClientId" + ocmClientSecretKey = "ocmClientSecret" ) // NewOCMClient creates a new OCM client. func NewOCMClient(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (*ocm.Client, error) { - token, url, err := ocmCredentials(ctx, rosaScope) + token, url, clientId, clientSecret, err := ocmCredentials(ctx, rosaScope) if err != nil { return nil, err } - return ocm.NewClient().Logger(logrus.New()).Config(&ocmcfg.Config{ - AccessToken: token, - URL: url, - }).Build() + + ocmConfig := ocmcfg.Config{ + URL: url, + } + + if clientId != "" && clientSecret != "" { + ocmConfig.ClientID = clientId + ocmConfig.ClientSecret = clientSecret + } else if token != "" { + rosaScope.Info(fmt.Sprintf("Using SSO offline token (%s) is deprecated, use service account credentials instead (%s and %s)", + ocmTokenKey, ocmClientIdKey, ocmClientSecretKey)) + + ocmConfig.AccessToken = token + } + + return ocm.NewClient().Logger(logrus.New()).Config(&ocmConfig).Build() } func newOCMRawConnection(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (*sdk.Connection, error) { - logger, err := sdk.NewGoLoggerBuilder(). + ocmSdkLogger, err := sdk.NewGoLoggerBuilder(). Debug(false). Build() if err != nil { return nil, fmt.Errorf("failed to build logger: %w", err) } - token, url, err := ocmCredentials(ctx, rosaScope) + + token, url, clientId, clientSecret, err := ocmCredentials(ctx, rosaScope) if err != nil { return nil, err } - connection, err := sdk.NewConnectionBuilder(). - Logger(logger). - Tokens(token). - URL(url). - Build() + connBuilder := sdk.NewConnectionBuilder(). + Logger(ocmSdkLogger). + URL(url) + + if clientId != "" && clientSecret != "" { + connBuilder.Client(clientId, clientSecret) + } else if token != "" { + rosaScope.Info(fmt.Sprintf("Using SSO offline token (%s) is deprecated, use service account credentials instead (%s and %s)", + ocmTokenKey, ocmClientIdKey, ocmClientSecretKey)) + + connBuilder.Tokens(token) + } + + connection, err := connBuilder.Build() if err != nil { return nil, fmt.Errorf("failed to create ocm connection: %w", err) } @@ -56,28 +81,45 @@ func newOCMRawConnection(ctx context.Context, rosaScope *scope.ROSAControlPlaneS return connection, nil } -func ocmCredentials(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (string, string, error) { - var token string - var ocmAPIUrl string +func ocmCredentials(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (string, string, string, string, error) { + var token string // Offline SSO token + var ocmClientId string // Service account client id + var ocmClientSecret string // Service account client secret + var ocmAPIUrl string // https://api.openshift.com by default + var secret *corev1.Secret - secret := rosaScope.CredentialsSecret() + secret = rosaScope.CredentialsSecret() // We'll retrieve the OCM credentials from the ROSA control plane if secret != nil { if err := rosaScope.Client.Get(ctx, client.ObjectKeyFromObject(secret), secret); err != nil { - return "", "", fmt.Errorf("failed to get credentials secret: %w", err) + return "", "", "", "", fmt.Errorf("failed to get credentials secret: %w", err) } - - token = string(secret.Data[ocmTokenKey]) - ocmAPIUrl = string(secret.Data[ocmAPIURLKey]) - } else { - // fallback to env variables if secrert is not set - token = os.Getenv("OCM_TOKEN") - if ocmAPIUrl = os.Getenv("OCM_API_URL"); ocmAPIUrl == "" { - ocmAPIUrl = "https://api.openshift.com" + } else { // If the reference to OCM secret wasn't specified in the ROSA control plane, we'll default to a predefined secret name + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default-rosa-creds-secret", + Namespace: rosaScope.Namespace(), + }, } } - if token == "" { - return "", "", fmt.Errorf("token is not provided, be sure to set OCM_TOKEN env variable or reference a credentials secret with key %s", ocmTokenKey) + if err := rosaScope.Client.Get(ctx, client.ObjectKeyFromObject(secret), secret); err != nil { + return "", "", "", "", fmt.Errorf("failed to get credentials secret: %w", err) } - return token, ocmAPIUrl, nil + + token = string(secret.Data[ocmTokenKey]) + ocmAPIUrl = string(secret.Data[ocmAPIURLKey]) + ocmClientId = string(secret.Data[ocmClientIdKey]) + ocmClientSecret = string(secret.Data[ocmClientSecretKey]) + + if token == "" && (ocmClientId == "" || ocmClientSecret == "") { + return "", "", "", "", + fmt.Errorf("OCM credentials have not been provided. Make sure to set the service account credentials (%s and %s) or the offline token (%s)", + ocmClientIdKey, ocmClientSecretKey, ocmTokenKey) + } + + if ocmAPIUrl == "" { + ocmAPIUrl = "https://api.openshift.com" // Defaults to production URL + } + + return token, ocmAPIUrl, ocmClientId, ocmClientSecret, nil }