Skip to content

Commit

Permalink
ROSA: Support for OCM service account credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
mzazrivec committed Dec 5, 2024
1 parent 9b65314 commit 1be3f7a
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 49 deletions.
24 changes: 6 additions & 18 deletions docs/book/src/topics/rosa/creating-a-cluster.md
Original file line number Diff line number Diff line change
@@ -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: "<token>"
- 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

Expand Down
104 changes: 73 additions & 31 deletions pkg/rosa/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,80 +4,122 @@ 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)
}

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
}

0 comments on commit 1be3f7a

Please sign in to comment.