mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
Feat: Machine Identity support for K8
This commit is contained in:
@ -20,12 +20,27 @@ const SERVICE_ACCOUNT_ACCESS_KEY = "serviceAccountAccessKey"
|
||||
const SERVICE_ACCOUNT_PUBLIC_KEY = "serviceAccountPublicKey"
|
||||
const SERVICE_ACCOUNT_PRIVATE_KEY = "serviceAccountPrivateKey"
|
||||
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId"
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret"
|
||||
|
||||
const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken"
|
||||
const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag
|
||||
const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config"
|
||||
const OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE = "infisical-operator-system"
|
||||
const INFISICAL_DOMAIN = "https://app.infisical.com/api"
|
||||
|
||||
type AuthStrategyType string
|
||||
|
||||
var AuthStrategy = struct {
|
||||
SERVICE_TOKEN AuthStrategyType
|
||||
SERVICE_ACCOUNT AuthStrategyType
|
||||
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
|
||||
}{
|
||||
SERVICE_TOKEN: "SERVICE_TOKEN",
|
||||
SERVICE_ACCOUNT: "SERVICE_ACCOUNT",
|
||||
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalConfigMap(ctx context.Context) (configMap map[string]string, errToReturn error) {
|
||||
// default key values
|
||||
defaultConfigMapData := make(map[string]string)
|
||||
@ -100,6 +115,28 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.
|
||||
return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalUniversalAuthMachineIdentityFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (machineIdentityDetails model.MachineIdentityDetails, err error) {
|
||||
|
||||
universalAuthCredsFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||
Namespace: infisicalSecret.Spec.Authentication.UniversalAuthMachineIdentity.Credentials.SecretNamespace,
|
||||
Name: infisicalSecret.Spec.Authentication.UniversalAuthMachineIdentity.Credentials.SecretName,
|
||||
})
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
return model.MachineIdentityDetails{}, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return model.MachineIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
clientIdFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_ID]
|
||||
clientSecretFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET]
|
||||
|
||||
return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil
|
||||
|
||||
}
|
||||
|
||||
// Fetches service account credentials from a Kubernetes secret specified in the infisicalSecret object, extracts the access key, public key, and private key from the secret, and returns them as a ServiceAccountCredentials object.
|
||||
// If any keys are missing or an error occurs, returns an empty object or an error object, respectively.
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (serviceAccountDetails model.ServiceAccountDetails, err error) {
|
||||
@ -127,7 +164,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub
|
||||
return model.ServiceAccountDetails{AccessKey: string(accessKeyFromSecret), PrivateKey: string(privateKeyFromSecret), PublicKey: string(publicKeyFromSecret)}, nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV3Response) error {
|
||||
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
plainProcessedSecrets := make(map[string][]byte)
|
||||
secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType
|
||||
|
||||
@ -156,7 +193,7 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
||||
}
|
||||
}
|
||||
|
||||
annotations[SECRET_VERSION_ANNOTATION] = encryptedSecretsResponse.ETag
|
||||
annotations[SECRET_VERSION_ANNOTATION] = ETag
|
||||
|
||||
// create a new secret as specified by the managed secret spec of CRD
|
||||
newKubeSecretInstance := &corev1.Secret{
|
||||
@ -187,16 +224,15 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, encryptedSecretsResponse api.GetEncryptedSecretsV3Response) error {
|
||||
func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
plainProcessedSecrets := make(map[string][]byte)
|
||||
for _, secret := range secretsFromAPI {
|
||||
plainProcessedSecrets[secret.Key] = []byte(secret.Value)
|
||||
}
|
||||
|
||||
managedKubeSecret.Data = plainProcessedSecrets
|
||||
managedKubeSecret.ObjectMeta.Annotations = map[string]string{
|
||||
SECRET_VERSION_ANNOTATION: encryptedSecretsResponse.ETag,
|
||||
}
|
||||
managedKubeSecret.ObjectMeta.Annotations = map[string]string{}
|
||||
managedKubeSecret.ObjectMeta.Annotations[SECRET_VERSION_ANNOTATION] = ETag
|
||||
|
||||
err := r.Client.Update(ctx, &managedKubeSecret)
|
||||
if err != nil {
|
||||
@ -213,11 +249,28 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
var authStrategy AuthStrategyType
|
||||
|
||||
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
infisicalMachineIdentityCreds, err := r.GetInfisicalUniversalAuthMachineIdentityFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" {
|
||||
authStrategy = AuthStrategy.SERVICE_ACCOUNT
|
||||
} else if infisicalToken != "" {
|
||||
authStrategy = AuthStrategy.SERVICE_TOKEN
|
||||
} else if infisicalMachineIdentityCreds.ClientId != "" && infisicalMachineIdentityCreds.ClientSecret != "" {
|
||||
authStrategy = AuthStrategy.UNIVERSAL_MACHINE_IDENTITY
|
||||
} else {
|
||||
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets")
|
||||
}
|
||||
|
||||
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load Infisical Token from the specified Kubernetes secret with error [%w]", err)
|
||||
@ -239,41 +292,66 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION]
|
||||
}
|
||||
|
||||
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
|
||||
var fullEncryptedSecretsResponse api.GetEncryptedSecretsV3Response
|
||||
if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY && util.MachineIdentityTokenInstance == nil {
|
||||
// Create new machine identity token instance
|
||||
util.MachineIdentityTokenInstance = util.NewMachineIdentityToken(infisicalMachineIdentityCreds.ClientId, infisicalMachineIdentityCreds.ClientSecret)
|
||||
}
|
||||
|
||||
if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" {
|
||||
plainTextSecretsFromApi, fullEncryptedSecretsResponse, err = util.GetPlainTextSecretsViaServiceAccount(serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
|
||||
// TODO: Also save a timestamp of when the token expires, so we know when to refetch an access token
|
||||
|
||||
// if infisicalMachineIdentityCreds.ClientId != "" && infisicalMachineIdentityCreds.ClientSecret != "" {
|
||||
// }
|
||||
|
||||
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
|
||||
var updateAttributes api.UpdateAttributes
|
||||
|
||||
if authStrategy == AuthStrategy.SERVICE_ACCOUNT { // Service Account
|
||||
plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaServiceAccount(serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account")
|
||||
|
||||
} else if infisicalToken != "" {
|
||||
} else if authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens (deprecated)
|
||||
envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
|
||||
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
|
||||
|
||||
plainTextSecretsFromApi, fullEncryptedSecretsResponse, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath)
|
||||
plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service token")
|
||||
} else if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY { // Machine Identity
|
||||
|
||||
accessToken, err := util.MachineIdentityTokenInstance.GetToken()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("\nReconcileInfisicalSecret: Waiting for access token to become available")
|
||||
return nil
|
||||
}
|
||||
|
||||
scope := infisicalSecret.Spec.Authentication.UniversalAuthMachineIdentity.SecretsScope
|
||||
plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaUniversalAuthMachineIdentity(accessToken, secretVersionBasedOnETag, scope)
|
||||
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via universal auth")
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets")
|
||||
}
|
||||
|
||||
if !fullEncryptedSecretsResponse.Modified {
|
||||
fmt.Println("No secrets modified so reconcile not needed", "Etag:", fullEncryptedSecretsResponse.ETag, "Modified:", fullEncryptedSecretsResponse.Modified)
|
||||
if !updateAttributes.Modified {
|
||||
fmt.Println("No secrets modified so reconcile not needed")
|
||||
return nil
|
||||
}
|
||||
|
||||
if managedKubeSecret == nil {
|
||||
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse)
|
||||
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateAttributes.ETag)
|
||||
} else {
|
||||
return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse)
|
||||
return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, updateAttributes.ETag)
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user