controllers/keycloakclient/chain/put_client.go (176 lines of code) (raw):
package chain
import (
"context"
"fmt"
"github.com/sethvargo/go-password/password"
corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
v1 "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"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
keycloakApi "github.com/epam/edp-keycloak-operator/api/v1"
"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/client/keycloak/dto"
"github.com/epam/edp-keycloak-operator/pkg/secretref"
)
const (
passwordLength = 36
passwordDigits = 9
passwordSymbols = 0
browserAuthFlow = "browser"
directGrantAuthFlow = "direct_grant"
)
// secretRef is an interface for getting secret from ref.
type secretRef interface {
GetSecretFromRef(ctx context.Context, refVal, secretNamespace string) (string, error)
}
type PutClient struct {
keycloakApiClient keycloak.Client
k8sClient client.Client
secretRef secretRef
}
func NewPutClient(keycloakApiClient keycloak.Client, k8sClient client.Client, secretRef secretRef) *PutClient {
return &PutClient{keycloakApiClient: keycloakApiClient, k8sClient: k8sClient, secretRef: secretRef}
}
func (el *PutClient) Serve(ctx context.Context, keycloakClient *keycloakApi.KeycloakClient, realmName string) error {
id, err := el.putKeycloakClient(ctx, keycloakClient, realmName)
if err != nil {
return fmt.Errorf("unable to put keycloak client: %w", err)
}
keycloakClient.Status.ClientID = id
return nil
}
func (el *PutClient) putKeycloakClient(ctx context.Context, keycloakClient *keycloakApi.KeycloakClient, realmName string) (string, error) {
log := ctrl.LoggerFrom(ctx)
log.Info("Start creation of Keycloak client")
var (
authFlowOverrides map[string]string
err error
)
if keycloakClient.Spec.AuthenticationFlowBindingOverrides != nil {
authFlowOverrides, err = el.getAuthFlows(keycloakClient, realmName)
if err != nil {
return "", fmt.Errorf("unable to get auth flows: %w", err)
}
}
clientDto, err := el.convertCrToDto(ctx, keycloakClient, realmName, authFlowOverrides)
if err != nil {
return "", fmt.Errorf("error during convertCrToDto: %w", err)
}
clientID, err := el.keycloakApiClient.GetClientID(clientDto.ClientId, clientDto.RealmName)
if err != nil && !adapter.IsErrNotFound(err) {
return "", fmt.Errorf("unable to check client id: %w", err)
}
if clientID != "" {
log.Info("Client already exists")
clientDto.ID = clientID
if updErr := el.keycloakApiClient.UpdateClient(ctx, clientDto); updErr != nil {
return "", fmt.Errorf("unable to update keycloak client: %w", updErr)
}
return clientID, nil
}
err = el.keycloakApiClient.CreateClient(ctx, clientDto)
if err != nil {
return "", fmt.Errorf("unable to create client: %w", err)
}
log.Info("End put keycloak client")
id, err := el.keycloakApiClient.GetClientID(clientDto.ClientId, clientDto.RealmName)
if err != nil {
return "", fmt.Errorf("unable to check client id: %w", err)
}
return id, nil
}
func (el *PutClient) convertCrToDto(ctx context.Context, keycloakClient *keycloakApi.KeycloakClient, realmName string, authflowOverrides map[string]string) (*dto.Client, error) {
if keycloakClient.Spec.Public {
res := dto.ConvertSpecToClient(&keycloakClient.Spec, "", realmName, authflowOverrides)
return res, nil
}
secret, err := el.getSecret(ctx, keycloakClient)
if err != nil {
return nil, fmt.Errorf("unable to get secret, err: %w", err)
}
return dto.ConvertSpecToClient(&keycloakClient.Spec, secret, realmName, authflowOverrides), nil
}
func (el *PutClient) getSecret(ctx context.Context, keycloakClient *keycloakApi.KeycloakClient) (string, error) {
if keycloakClient.Spec.Secret != "" {
// We need to set secret in a new format for old clients for backward compatibility.
// TODO: This code can be removed in the future.
if !secretref.HasSecretRef(keycloakClient.Spec.Secret) {
if err := el.setSecretRef(ctx, keycloakClient); err != nil {
return "", err
}
}
secretVal, err := el.secretRef.GetSecretFromRef(ctx, keycloakClient.Spec.Secret, keycloakClient.Namespace)
if err != nil {
return "", fmt.Errorf("unable to get secret from ref: %w", err)
}
return secretVal, nil
}
return el.generateSecret(ctx, keycloakClient)
}
func (el *PutClient) generateSecret(ctx context.Context, keycloakClient *keycloakApi.KeycloakClient) (string, error) {
var clientSecret corev1.Secret
secretName := fmt.Sprintf("keycloak-client-%s-secret", keycloakClient.Name)
secretErr := el.k8sClient.Get(ctx, types.NamespacedName{Namespace: keycloakClient.Namespace,
Name: secretName}, &clientSecret)
if secretErr != nil && !k8sErrors.IsNotFound(secretErr) {
return "", fmt.Errorf("unable to check client secret existance: %w", secretErr)
}
pass, err := password.Generate(passwordLength, passwordDigits, passwordSymbols, true, true)
if err != nil {
return "", fmt.Errorf("unable to generate password: %w", err)
}
if k8sErrors.IsNotFound(secretErr) {
clientSecret = corev1.Secret{
ObjectMeta: v1.ObjectMeta{Namespace: keycloakClient.Namespace,
Name: secretName},
Data: map[string][]byte{
keycloakApi.ClientSecretKey: []byte(pass),
},
}
if err := controllerutil.SetControllerReference(keycloakClient, &clientSecret, el.k8sClient.Scheme()); err != nil {
return "", fmt.Errorf("unable to set controller ref for secret: %w", err)
}
if err := el.k8sClient.Create(ctx, &clientSecret); err != nil {
return "", fmt.Errorf("unable to create secret %+v, err: %w", clientSecret, err)
}
}
keycloakClient.Spec.Secret = secretref.GenerateSecretRef(clientSecret.Name, keycloakApi.ClientSecretKey)
if err := el.k8sClient.Update(ctx, keycloakClient); err != nil {
return "", fmt.Errorf("unable to update client with new secret: %s, err: %w", clientSecret.Name, err)
}
return string(clientSecret.Data[keycloakApi.ClientSecretKey]), nil
}
func (el *PutClient) setSecretRef(ctx context.Context, keycloakClient *keycloakApi.KeycloakClient) error {
ref := secretref.GenerateSecretRef(keycloakClient.Spec.Secret, keycloakApi.ClientSecretKey)
keycloakClient.Spec.Secret = ref
if err := el.k8sClient.Update(ctx, keycloakClient); err != nil {
return fmt.Errorf("unable to update client with secret ref %s: %w", ref, err)
}
return nil
}
func (el *PutClient) getAuthFlows(keycloakClient *keycloakApi.KeycloakClient, realmName string) (map[string]string, error) {
clientAuthFlows := keycloakClient.Spec.AuthenticationFlowBindingOverrides
flows, err := el.keycloakApiClient.GetRealmAuthFlows(realmName)
if err != nil {
return nil, fmt.Errorf("unable to get realm: %w", err)
}
realmAuthFlows := make(map[string]string)
for i := range flows {
realmAuthFlows[flows[i].Alias] = flows[i].ID
}
authFlowOverrides := make(map[string]string)
if clientAuthFlows.Browser != "" {
if _, ok := realmAuthFlows[clientAuthFlows.Browser]; !ok {
return nil, fmt.Errorf("auth flow %s not found in realm %s", clientAuthFlows.Browser, realmName)
}
authFlowOverrides[browserAuthFlow] = realmAuthFlows[clientAuthFlows.Browser]
}
if clientAuthFlows.DirectGrant != "" {
if _, ok := realmAuthFlows[clientAuthFlows.DirectGrant]; !ok {
return nil, fmt.Errorf("auth flow %s not found in realm %s", clientAuthFlows.DirectGrant, realmName)
}
authFlowOverrides[directGrantAuthFlow] = realmAuthFlows[clientAuthFlows.DirectGrant]
}
return authFlowOverrides, nil
}