controllers/helper/controller_helper_auth.go (261 lines of code) (raw):

package helper import ( "context" "fmt" "github.com/pkg/errors" coreV1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" keycloakApi "github.com/epam/edp-keycloak-operator/api/v1" keycloakAlpha "github.com/epam/edp-keycloak-operator/api/v1alpha1" "github.com/epam/edp-keycloak-operator/pkg/client/keycloak" "github.com/epam/edp-keycloak-operator/pkg/client/keycloak/adapter" "github.com/epam/edp-keycloak-operator/pkg/secretref" ) const ( keycloakTokenSecretPrefix = "kc-token-" keycloakTokenSecretKey = "token" ) var ErrKeycloakIsNotAvailable = errors.New("keycloak is not available") // KeycloakAuthData contains data for keycloak authentication. type KeycloakAuthData struct { // Url is keycloak url. Url string // SecretName is name of secret with keycloak credentials. SecretName string // SecretNamespace is namespace of secret with keycloak credentials. SecretNamespace string // AdminType is type of keycloak admin. AdminType string // KeycloakCRName is name of keycloak CR. KeycloakCRName string // CACert is root certificate authority. CACert string `json:"caCert,omitempty"` // InsecureSkipVerify controls whether api client verifies the server's certificate chain and host name. InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` } func (h *Helper) CreateKeycloakClientFromRealmRef(ctx context.Context, object ObjectWithRealmRef) (keycloak.Client, error) { authData, err := h.getKeycloakAuthDataFromRealmRef(ctx, object) if err != nil { return nil, err } return h.CreateKeycloakClientFomAuthData(ctx, authData) } func (h *Helper) CreateKeycloakClientFromRealm(ctx context.Context, realm *keycloakApi.KeycloakRealm) (keycloak.Client, error) { authData, err := h.getKeycloakAuthDataFromRealm(ctx, realm) if err != nil { return nil, err } return h.CreateKeycloakClientFomAuthData(ctx, authData) } func (h *Helper) CreateKeycloakClientFromClusterRealm(ctx context.Context, realm *keycloakAlpha.ClusterKeycloakRealm) (keycloak.Client, error) { authData, err := h.getKeycloakAuthDataFromClusterRealm(ctx, realm) if err != nil { return nil, err } return h.CreateKeycloakClientFomAuthData(ctx, authData) } func (h *Helper) CreateKeycloakClient(ctx context.Context, url, user, password, adminType, caCert string, insecureSkipVerify bool) (keycloak.Client, error) { clientAdapter, err := h.adapterBuilder( ctx, adapter.GoCloakConfig{ Url: url, User: user, Password: password, RootCertificate: caCert, InsecureSkipVerify: insecureSkipVerify, }, adminType, ctrl.LoggerFrom(ctx), h.restyClient) if err != nil { return nil, errors.Wrap(err, "unable to init kc client adapter") } return clientAdapter, nil } func (h *Helper) InvalidateKeycloakClientTokenSecret(ctx context.Context, namespace, rootKeycloakName string) error { var secret coreV1.Secret if err := h.client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: tokenSecretName(rootKeycloakName)}, &secret); err != nil { return errors.Wrap(err, "unable to get client token secret") } if err := h.client.Delete(ctx, &secret); err != nil { return errors.Wrap(err, "unable to delete client token secret") } return nil } func (h *Helper) CreateKeycloakClientFomAuthData(ctx context.Context, authData *KeycloakAuthData) (keycloak.Client, error) { h.tokenSecretLock.Lock() defer h.tokenSecretLock.Unlock() clientAdapter, err := h.createKeycloakClientFromTokenSecret(ctx, authData) if err == nil { return clientAdapter, nil } if !k8sErrors.IsNotFound(err) && !adapter.IsErrTokenExpired(err) { return nil, fmt.Errorf("unable to create kc client from token secret: %w", err) } clientAdapter, err = h.createKeycloakClientFromLoginPassword(ctx, authData) if err != nil { return nil, fmt.Errorf("unable to create kc client from login password: %w", err) } return clientAdapter, nil } func (h *Helper) createKeycloakClientFromLoginPassword(ctx context.Context, authData *KeycloakAuthData) (keycloak.Client, error) { var secret coreV1.Secret if err := h.client.Get(ctx, types.NamespacedName{ Name: authData.SecretName, Namespace: authData.SecretNamespace, }, &secret); err != nil { return nil, errors.Wrap(err, "authData login password secret not found") } clientAdapter, err := h.CreateKeycloakClient(ctx, authData.Url, string(secret.Data["username"]), string(secret.Data["password"]), authData.AdminType, authData.CACert, authData.InsecureSkipVerify) if err != nil { return nil, errors.Wrap(err, "unable to init authData client adapter") } jwtToken, err := clientAdapter.ExportToken() if err != nil { return nil, errors.Wrap(err, "unable to export authData client token") } if err := h.saveKeycloakClientTokenSecret(ctx, tokenSecretName(authData.SecretName), secret.Namespace, jwtToken); err != nil { return nil, errors.Wrap(err, "unable to save authData token to secret") } return clientAdapter, nil } func (h *Helper) createKeycloakClientFromTokenSecret(ctx context.Context, authData *KeycloakAuthData) (keycloak.Client, error) { var tokenSecret coreV1.Secret if err := h.client.Get(ctx, types.NamespacedName{ Name: tokenSecretName(authData.KeycloakCRName), Namespace: authData.SecretNamespace, }, &tokenSecret); err != nil { return nil, errors.Wrap(err, "unable to get token secret") } clientAdapter, err := adapter.MakeFromToken(adapter.GoCloakConfig{ Url: authData.Url, RootCertificate: authData.CACert, InsecureSkipVerify: authData.InsecureSkipVerify, }, tokenSecret.Data[keycloakTokenSecretKey], ctrl.LoggerFrom(ctx)) if err != nil { return nil, errors.Wrap(err, "unable to make authData client from token") } return clientAdapter, nil } func (h *Helper) saveKeycloakClientTokenSecret(ctx context.Context, secretName, secretNamespace string, token []byte) error { var secret coreV1.Secret err := h.client.Get(ctx, types.NamespacedName{Namespace: secretNamespace, Name: secretName}, &secret) if err == nil { secret.Data = map[string][]byte{ keycloakTokenSecretKey: token, } if err = h.client.Update(ctx, &secret); err != nil { return errors.Wrap(err, "unable to update token secret") } return nil } if k8sErrors.IsNotFound(err) { secret = coreV1.Secret{ObjectMeta: metav1.ObjectMeta{ Namespace: secretNamespace, Name: secretName, }, Data: map[string][]byte{ keycloakTokenSecretKey: token, }} if err = h.client.Create(ctx, &secret); err != nil { return errors.Wrap(err, "unable to create token secret") } return nil } return errors.Wrap(err, "error during token secret retrieval") } func (h *Helper) getKeycloakAuthDataFromRealmRef(ctx context.Context, object ObjectWithRealmRef) (*KeycloakAuthData, error) { kind := object.GetRealmRef().Kind name := object.GetRealmRef().Name switch kind { case keycloakApi.KeycloakRealmKind: realm := &keycloakApi.KeycloakRealm{} if err := h.client.Get(ctx, types.NamespacedName{Name: name, Namespace: object.GetNamespace()}, realm); err != nil { return nil, fmt.Errorf("unable to get realm: %w", err) } return h.getKeycloakAuthDataFromRealm(ctx, realm) case keycloakAlpha.ClusterKeycloakRealmKind: clusterRealm := &keycloakAlpha.ClusterKeycloakRealm{} if err := h.client.Get(ctx, types.NamespacedName{Name: name}, clusterRealm); err != nil { return nil, fmt.Errorf("unable to get cluster realm: %w", err) } return h.getKeycloakAuthDataFromClusterRealm(ctx, clusterRealm) default: return nil, fmt.Errorf("unknown realm kind: %s", kind) } } func (h *Helper) getKeycloakAuthDataFromRealm(ctx context.Context, realm *keycloakApi.KeycloakRealm) (*KeycloakAuthData, error) { kind := realm.Spec.KeycloakRef.Kind name := realm.Spec.KeycloakRef.Name switch kind { case keycloakApi.KeycloakKind: kc := &keycloakApi.Keycloak{} if err := h.client.Get(ctx, types.NamespacedName{Name: name, Namespace: realm.GetNamespace()}, kc); err != nil { return nil, fmt.Errorf("unable to get keycloak: %w", err) } if !kc.Status.Connected { return nil, ErrKeycloakIsNotAvailable } return MakeKeycloakAuthDataFromKeycloak(ctx, kc, h.client) case keycloakAlpha.ClusterKeycloakKind: kc := &keycloakAlpha.ClusterKeycloak{} if err := h.client.Get(ctx, types.NamespacedName{Name: name}, kc); err != nil { return nil, fmt.Errorf("unable to get cluster keycloak: %w", err) } if !kc.Status.Connected { return nil, ErrKeycloakIsNotAvailable } return MakeKeycloakAuthDataFromClusterKeycloak(ctx, kc, h.operatorNamespace, h.client) default: return nil, fmt.Errorf("unknown keycloak kind: %s", kind) } } func (h *Helper) getKeycloakAuthDataFromClusterRealm(ctx context.Context, realm *keycloakAlpha.ClusterKeycloakRealm) (*KeycloakAuthData, error) { kc := &keycloakAlpha.ClusterKeycloak{} if err := h.client.Get(ctx, types.NamespacedName{Name: realm.GetKeycloakRef().Name}, kc); err != nil { return nil, fmt.Errorf("unable to get cluster keycloak: %w", err) } if !kc.Status.Connected { return nil, ErrKeycloakIsNotAvailable } return MakeKeycloakAuthDataFromClusterKeycloak(ctx, kc, h.operatorNamespace, h.client) } func MakeKeycloakAuthDataFromKeycloak( ctx context.Context, keycloak *keycloakApi.Keycloak, k8sClient client.Client, ) (*KeycloakAuthData, error) { auth := &KeycloakAuthData{ Url: keycloak.Spec.Url, SecretName: keycloak.Spec.Secret, SecretNamespace: keycloak.Namespace, AdminType: keycloak.Spec.AdminType, KeycloakCRName: keycloak.Name, InsecureSkipVerify: keycloak.Spec.InsecureSkipVerify, } caCert, err := secretref.GetValueFromSourceRef(ctx, keycloak.Spec.CACert, keycloak.Namespace, k8sClient) if err != nil { return nil, fmt.Errorf("unable to get ca cert: %w", err) } auth.CACert = caCert return auth, nil } func MakeKeycloakAuthDataFromClusterKeycloak( ctx context.Context, keycloak *keycloakAlpha.ClusterKeycloak, secretNamespace string, k8sClient client.Client, ) (*KeycloakAuthData, error) { auth := &KeycloakAuthData{ Url: keycloak.Spec.Url, SecretName: keycloak.Spec.Secret, SecretNamespace: secretNamespace, AdminType: keycloak.Spec.AdminType, KeycloakCRName: keycloak.Name, InsecureSkipVerify: keycloak.Spec.InsecureSkipVerify, } caCert, err := secretref.GetValueFromSourceRef(ctx, keycloak.Spec.CACert, secretNamespace, k8sClient) if err != nil { return nil, fmt.Errorf("unable to get ca cert: %w", err) } auth.CACert = caCert return auth, nil } func tokenSecretName(keycloakName string) string { return fmt.Sprintf("%s%s", keycloakTokenSecretPrefix, keycloakName) }