From f869def8ea849f16a47fa6e41ac3b7a67673df91 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:13:28 +0100 Subject: [PATCH 01/31] Added new types --- .../api/v1alpha1/infisicalsecret_types.go | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index cae966fe0..ee2daed95 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -9,12 +9,20 @@ type Authentication struct { ServiceAccount ServiceAccountDetails `json:"serviceAccount"` // +kubebuilder:validation:Optional ServiceToken ServiceTokenDetails `json:"serviceToken"` + // +kubebuilder:validation:Optional + UniversalAuthMachineIdentity UniversalAuthMachineIdentityDetails `json:"universalAuthMachineIdentity"` +} + +type UniversalAuthMachineIdentityDetails struct { + // +kubebuilder:validation:Required + Credentials KubeSecretReference `json:"credentials"` + // +kubebuilder:validation:Required + SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"` } type ServiceTokenDetails struct { // +kubebuilder:validation:Required ServiceTokenSecretReference KubeSecretReference `json:"serviceTokenSecretReference"` - // +kubebuilder:validation:Required SecretsScope SecretScopeInWorkspace `json:"secretsScope"` } @@ -28,11 +36,19 @@ type ServiceAccountDetails struct { type SecretScopeInWorkspace struct { // +kubebuilder:validation:Required SecretsPath string `json:"secretsPath"` - // +kubebuilder:validation:Required EnvSlug string `json:"envSlug"` } +type MachineIdentityScopeInWorkspace struct { + // +kubebuilder:validation:Required + SecretsPath string `json:"secretsPath"` + // +kubebuilder:validation:Required + EnvSlug string `json:"envSlug"` + // +kubebuilder:validation:Required + ProjectId string `json:"projectId"` +} + type KubeSecretReference struct { // The name of the Kubernetes Secret // +kubebuilder:validation:Required From 0f3a48bb324fcfc2a9ae11ea86f86bf30e443217 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:14:34 +0100 Subject: [PATCH 02/31] Generated --- .../api/v1alpha1/zz_generated.deepcopy.go | 33 +++++++++++++++++++ ...ecrets.infisical.com_infisicalsecrets.yaml | 32 ++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index 5b6befcbc..bab60dbfd 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -31,6 +31,7 @@ func (in *Authentication) DeepCopyInto(out *Authentication) { *out = *in out.ServiceAccount = in.ServiceAccount out.ServiceToken = in.ServiceToken + out.UniversalAuthMachineIdentity = in.UniversalAuthMachineIdentity } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authentication. @@ -157,6 +158,21 @@ func (in *KubeSecretReference) DeepCopy() *KubeSecretReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineIdentityScopeInWorkspace) DeepCopyInto(out *MachineIdentityScopeInWorkspace) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineIdentityScopeInWorkspace. +func (in *MachineIdentityScopeInWorkspace) DeepCopy() *MachineIdentityScopeInWorkspace { + if in == nil { + return nil + } + out := new(MachineIdentityScopeInWorkspace) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MangedKubeSecretConfig) DeepCopyInto(out *MangedKubeSecretConfig) { *out = *in @@ -219,3 +235,20 @@ func (in *ServiceTokenDetails) DeepCopy() *ServiceTokenDetails { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UniversalAuthMachineIdentityDetails) DeepCopyInto(out *UniversalAuthMachineIdentityDetails) { + *out = *in + out.Credentials = in.Credentials + out.SecretsScope = in.SecretsScope +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UniversalAuthMachineIdentityDetails. +func (in *UniversalAuthMachineIdentityDetails) DeepCopy() *UniversalAuthMachineIdentityDetails { + if in == nil { + return nil + } + out := new(UniversalAuthMachineIdentityDetails) + in.DeepCopyInto(out) + return out +} diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index 7d4e88b14..f6d5b05a9 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -90,6 +90,38 @@ spec: - secretsScope - serviceTokenSecretReference type: object + universalAuthMachineIdentity: + properties: + credentials: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret + is located + type: string + required: + - secretName + - secretNamespace + type: object + secretsScope: + properties: + envSlug: + type: string + projectId: + type: string + secretsPath: + type: string + required: + - envSlug + - projectId + - secretsPath + type: object + required: + - credentials + - secretsScope + type: object type: object hostAPI: description: Infisical host to pull secrets from From 9e85d9bbf0058f93a3279d716b07286eda30a29f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:15:06 +0100 Subject: [PATCH 03/31] Example --- k8-operator/config/samples/machineIdentitySecret.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 k8-operator/config/samples/machineIdentitySecret.yaml diff --git a/k8-operator/config/samples/machineIdentitySecret.yaml b/k8-operator/config/samples/machineIdentitySecret.yaml new file mode 100644 index 000000000..c2a69b438 --- /dev/null +++ b/k8-operator/config/samples/machineIdentitySecret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: universal-auth-credentials +type: Opaque +stringData: + clientId: + clientSecret: From ff2098408d244a9b55932ffebe40005fa4e46863 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:18:06 +0100 Subject: [PATCH 04/31] Update sample.yaml --- k8-operator/config/samples/sample.yaml | 31 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml index ca0025a53..73240fafb 100644 --- a/k8-operator/config/samples/sample.yaml +++ b/k8-operator/config/samples/sample.yaml @@ -29,7 +29,30 @@ spec: creationPolicy: "Orphan" ## Owner | Orphan # secretType: kubernetes.io/dockerconfigjson - # # To be depreciated soon - # tokenSecretReference: - # secretName: service-token - # secretNamespace: default + serviceToken: + serviceTokenSecretReference: + secretName: service-token + secretNamespace: default + secretsScope: + envSlug: + secretsPath: # Root is "/" + + universalAuthMachineIdentity: + secretsScope: + projectId: "" # TODO: Make this a slug + envSlug: # "dev", "staging", "prod", etc.. + secretsPath: # Root is "/" + + credentials: + secretName: universal-auth-credentials + secretNamespace: default + + managedSecretReference: + secretName: managed-secret + secretNamespace: default + # secretType: kubernetes.io/dockerconfigjson + + # # To be depreciated soon + # tokenSecretReference: + # secretName: service-token + # secretNamespace: default From 6f53a5631c5560838fabebca16eeb5587829a2bf Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:18:24 +0100 Subject: [PATCH 05/31] Fix: Double prints --- k8-operator/controllers/infisicalsecret_controller.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/k8-operator/controllers/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret_controller.go index 7aca83651..5eba262ac 100644 --- a/k8-operator/controllers/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret_controller.go @@ -53,11 +53,11 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ if infisicalSecretCR.Spec.ResyncInterval != 0 { requeueTime = time.Second * time.Duration(infisicalSecretCR.Spec.ResyncInterval) - fmt.Println("Manual re-sync interval set", "requeueAfter", requeueTime) + fmt.Printf("\nManual re-sync interval set. Interval: %v\n", requeueTime) + } else { + fmt.Printf("\nRe-sync interval set. Interval: %v\n", requeueTime) } - fmt.Println("Requeue duration set", "requeueAfter", requeueTime) - // Check if the resource is already marked for deletion if infisicalSecretCR.GetDeletionTimestamp() != nil { return ctrl.Result{ From 872a28d02a93d0095cfdb03bfa8479ab06297ff7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:18:34 +0100 Subject: [PATCH 06/31] Feat: Machine Identity support for K8 --- .../controllers/infisicalsecret_helper.go | 110 +++++++++++++++--- 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index 4523b42bb..9de1121fd 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -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) } } From 318d12addd997458a1c2bfa2b20bf58cfb2c7711 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:18:43 +0100 Subject: [PATCH 07/31] Feat: Machine Identity support --- k8-operator/packages/api/api.go | 80 ++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 7 deletions(-) diff --git a/k8-operator/packages/api/api.go b/k8-operator/packages/api/api.go index 15de04321..e69930ce1 100644 --- a/k8-operator/packages/api/api.go +++ b/k8-operator/packages/api/api.go @@ -58,7 +58,6 @@ func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Req R(). SetResult(&secretsResponse). SetHeader("User-Agent", USER_AGENT_NAME). - SetHeader("If-None-Match", request.ETag). SetQueryParam("environment", request.Environment). SetQueryParam("include_imports", "true"). // TODO needs to be set as a option SetQueryParam("workspaceId", request.WorkspaceId) @@ -77,13 +76,10 @@ func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Req return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response) } - if response.Header().Get("etag") == request.ETag { - secretsResponse.Modified = false - } else { - secretsResponse.Modified = true - } + responseETag := response.Header().Get("etag") - secretsResponse.ETag = response.Header().Get("etag") + secretsResponse.Modified = request.ETag != responseETag + secretsResponse.ETag = responseETag return secretsResponse, nil } @@ -107,6 +103,76 @@ func CallGetServiceTokenAccountDetailsV2(httpClient *resty.Client) (ServiceAccou return serviceAccountDetailsResponse, nil } +func CallUniversalMachineIdentityLogin(request MachineIdentityUniversalAuthLoginRequest) (MachineIdentityDetailsResponse, error) { + var machineIdentityDetailsResponse MachineIdentityDetailsResponse + + response, err := resty.New(). + R(). + SetResult(&machineIdentityDetailsResponse). + SetBody(request). + SetHeader("User-Agent", USER_AGENT_NAME). + Post(fmt.Sprintf("%v/v1/auth/universal-auth/login", API_HOST_URL)) + + if err != nil { + return MachineIdentityDetailsResponse{}, fmt.Errorf("CallUniversalMachineIdentityLogin: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return MachineIdentityDetailsResponse{}, fmt.Errorf("CallUniversalMachineIdentityLogin: Unsuccessful response: [response=%s]", response) + } + + return machineIdentityDetailsResponse, nil +} + +func CallUniversalMachineIdentityRefreshAccessToken(request MachineIdentityUniversalAuthRefreshRequest) (MachineIdentityDetailsResponse, error) { + var universalAuthRefreshResponse MachineIdentityDetailsResponse + + response, err := resty.New(). + R(). + SetResult(&universalAuthRefreshResponse). + SetHeader("User-Agent", USER_AGENT_NAME). + SetBody(request). + Post(fmt.Sprintf("%v/v1/auth/token/renew", API_HOST_URL)) + + if err != nil { + return MachineIdentityDetailsResponse{}, fmt.Errorf("CallUniversalAuthRefreshAccessToken: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return MachineIdentityDetailsResponse{}, fmt.Errorf("CallUniversalAuthRefreshAccessToken: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) + } + + return universalAuthRefreshResponse, nil +} + +func CallGetDecryptedSecretsV3(httpClient *resty.Client, request GetDecryptedSecretsV3Request) (GetDecryptedSecretsV3Response, error) { + var decryptedSecretsResponse GetDecryptedSecretsV3Response + + response, err := httpClient. + R(). + SetResult(&decryptedSecretsResponse). + SetHeader("User-Agent", USER_AGENT_NAME). + SetQueryParam("secretPath", request.SecretPath). + SetQueryParam("workspaceId", request.ProjectID). + SetQueryParam("environment", request.Environment). + Get(fmt.Sprintf("%v/v3/secrets/raw", API_HOST_URL)) + + if err != nil { + return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return GetDecryptedSecretsV3Response{}, fmt.Errorf("CallGetDecryptedSecretsV3: Unsuccessful response: [response=%s]", response) + } + + responseETag := response.Header().Get("etag") + + decryptedSecretsResponse.Modified = request.ETag != responseETag + decryptedSecretsResponse.ETag = responseETag + + return decryptedSecretsResponse, nil +} + func CallGetServiceAccountWorkspacePermissionsV2(httpClient *resty.Client) (ServiceAccountWorkspacePermissions, error) { var serviceAccountWorkspacePermissionsResponse ServiceAccountWorkspacePermissions response, err := httpClient. From f8f2b2574d492202a65cedcb4f3fef82929d33fa Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:21:16 +0100 Subject: [PATCH 08/31] Feat: Machine Identity support (types) --- .../controllers/infisicalsecret_helper.go | 3 +- k8-operator/packages/api/models.go | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index 9de1121fd..622d1fa5d 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" - "github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/model" "github.com/Infisical/infisical/k8-operator/packages/util" corev1 "k8s.io/api/core/v1" @@ -303,7 +302,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context // } var plainTextSecretsFromApi []model.SingleEnvironmentVariable - var updateAttributes api.UpdateAttributes + var updateAttributes model.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) diff --git a/k8-operator/packages/api/models.go b/k8-operator/packages/api/models.go index a72ccfc49..fa618791b 100644 --- a/k8-operator/packages/api/models.go +++ b/k8-operator/packages/api/models.go @@ -65,6 +65,17 @@ type EncryptedSecretV3 struct { UpdatedAt time.Time `json:"updatedAt"` } +type DecryptedSecretV3 struct { + ID string `json:"id"` + Workspace string `json:"workspace"` + Environment string `json:"environment"` + Version int `json:"version"` + Type string `json:"string"` + SecretKey string `json:"secretKey"` + SecretValue string `json:"secretValue"` + SecretComment string `json:"secretComment"` +} + type ImportedSecretV3 struct { Environment string `json:"environment"` FolderId string `json:"folderId"` @@ -79,6 +90,19 @@ type GetEncryptedSecretsV3Response struct { ETag string `json:"ETag,omitempty"` } +type GetDecryptedSecretsV3Response struct { + Secrets []DecryptedSecretV3 `json:"secrets"` + ETag string `json:"ETag,omitempty"` + Modified bool `json:"modified,omitempty"` +} + +type GetDecryptedSecretsV3Request struct { + ProjectID string `json:"projectId"` + Environment string `json:"environment"` + SecretPath string `json:"secretPath"` + ETag string `json:"etag,omitempty"` +} + type GetServiceTokenDetailsResponse struct { ID string `json:"_id"` Name string `json:"name"` @@ -101,6 +125,13 @@ type ServiceAccountDetailsResponse struct { } `json:"serviceAccount"` } +type MachineIdentityDetailsResponse struct { + AccessToken string `json:"accessToken"` + ExpiresIn int `json:"expiresIn"` + AccessTokenMaxTTL int `json:"accessTokenMaxTTL"` + TokenType string `json:"tokenType"` +} + type ServiceAccountWorkspacePermission struct { ID string `json:"_id"` ServiceAccount string `json:"serviceAccount"` @@ -128,6 +159,15 @@ type GetServiceAccountKeysRequest struct { ServiceAccountId string `json:"id"` } +type MachineIdentityUniversalAuthLoginRequest struct { + ClientId string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} + +type MachineIdentityUniversalAuthRefreshRequest struct { + AccessToken string `json:"accessToken"` +} + type ServiceAccountKey struct { ID string `json:"_id"` EncryptedKey string `json:"encryptedKey"` From 83bbf9599da8a6d1ac55849bba6645f1ba70016e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:21:26 +0100 Subject: [PATCH 09/31] Feat: Machine Identity support --- k8-operator/packages/model/model.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/k8-operator/packages/model/model.go b/k8-operator/packages/model/model.go index b7d4780b2..17cbb9835 100644 --- a/k8-operator/packages/model/model.go +++ b/k8-operator/packages/model/model.go @@ -6,6 +6,16 @@ type ServiceAccountDetails struct { PrivateKey string } +type MachineIdentityDetails struct { + ClientId string + ClientSecret string +} + +type UpdateAttributes struct { + Modified bool + ETag string +} + type SingleEnvironmentVariable struct { Key string `json:"key"` Value string `json:"value"` From 55780b65d3606377b43a216a5fce59926a8cf83f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:21:34 +0100 Subject: [PATCH 10/31] Feat: Machine Identity support (token refreshing logic) --- .../packages/util/machine-identity-token.go | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 k8-operator/packages/util/machine-identity-token.go diff --git a/k8-operator/packages/util/machine-identity-token.go b/k8-operator/packages/util/machine-identity-token.go new file mode 100644 index 000000000..77c1ad0af --- /dev/null +++ b/k8-operator/packages/util/machine-identity-token.go @@ -0,0 +1,170 @@ +package util + +import ( + "fmt" + "os" + "sync" + "time" + + "github.com/Infisical/infisical/k8-operator/packages/api" + "github.com/go-resty/resty/v2" +) + +type MachineIdentityToken struct { + accessTokenTTL time.Duration + accessTokenMaxTTL time.Duration + accessTokenFetchedTime time.Time + accessTokenRefreshedTime time.Time + + mutex sync.Mutex + + accessToken string + clientSecret string + clientId string +} + +func NewMachineIdentityToken(clientId string, clientSecret string) *MachineIdentityToken { + + token := MachineIdentityToken{ + clientSecret: clientSecret, + clientId: clientId, + } + + go token.HandleTokenLifecycle() + + return &token +} + +func (tm *MachineIdentityToken) HandleTokenLifecycle() error { + + for { + accessTokenMaxTTLExpiresInTime := tm.accessTokenFetchedTime.Add(tm.accessTokenMaxTTL - (5 * time.Second)) + accessTokenRefreshedTime := tm.accessTokenRefreshedTime + + if accessTokenRefreshedTime.IsZero() { + accessTokenRefreshedTime = tm.accessTokenFetchedTime + } + + nextAccessTokenExpiresInTime := accessTokenRefreshedTime.Add(tm.accessTokenTTL - (5 * time.Second)) + + if tm.accessTokenFetchedTime.IsZero() && tm.accessTokenRefreshedTime.IsZero() { + // case: init login to get access token + fmt.Println("\nInfisical Authentication: attempting to authenticate...") + err := tm.FetchNewAccessToken() + if err != nil { + fmt.Printf("\nInfisical Authentication: unable to authenticate universal auth because %v. Will retry in 30 seconds", err) + + // wait a bit before trying again + time.Sleep((30 * time.Second)) + continue + } + } else if time.Now().After(accessTokenMaxTTLExpiresInTime) { + fmt.Printf("\nInfisical Authentication: machine identity access token has reached max ttl, attempting to re authenticate...") + err := tm.FetchNewAccessToken() + if err != nil { + fmt.Printf("\nInfisical Authentication: unable to authenticate universal auth because %v. Will retry in 30 seconds", err) + + // wait a bit before trying again + time.Sleep((30 * time.Second)) + continue + } + } else { + err := tm.RefreshAccessToken() + if err != nil { + fmt.Printf("\nInfisical Authentication: unable to refresh universal auth token because %v. Will retry in 30 seconds", err) + + // wait a bit before trying again + time.Sleep((30 * time.Second)) + continue + } + } + + if accessTokenRefreshedTime.IsZero() { + accessTokenRefreshedTime = tm.accessTokenFetchedTime + } else { + accessTokenRefreshedTime = tm.accessTokenRefreshedTime + } + + nextAccessTokenExpiresInTime = accessTokenRefreshedTime.Add(tm.accessTokenTTL - (5 * time.Second)) + accessTokenMaxTTLExpiresInTime = tm.accessTokenFetchedTime.Add(tm.accessTokenMaxTTL - (5 * time.Second)) + + if nextAccessTokenExpiresInTime.After(accessTokenMaxTTLExpiresInTime) { + // case: Refreshed so close that the next refresh would occur beyond max ttl (this is because currently, token renew tries to add +access-token-ttl amount of time) + // example: access token ttl is 11 sec and max ttl is 30 sec. So it will start with 11 seconds, then 22 seconds but the next time you call refresh it would try to extend it to 33 but max ttl only allows 30, so the token will be valid until 30 before we need to reauth + time.Sleep(tm.accessTokenTTL - nextAccessTokenExpiresInTime.Sub(accessTokenMaxTTLExpiresInTime)) + } else { + time.Sleep(tm.accessTokenTTL - (5 * time.Second)) + } + } +} + +func (tm *MachineIdentityToken) RefreshAccessToken() error { + httpClient := resty.New() + httpClient.SetRetryCount(10000). + SetRetryMaxWaitTime(20 * time.Second). + SetRetryWaitTime(5 * time.Second) + + accessToken, err := tm.GetToken() + + if err != nil { + return err + } + + response, err := api.CallUniversalMachineIdentityRefreshAccessToken(api.MachineIdentityUniversalAuthRefreshRequest{AccessToken: accessToken}) + if err != nil { + return err + } + + accessTokenTTL := time.Duration(response.ExpiresIn * int(time.Second)) + accessTokenMaxTTL := time.Duration(response.AccessTokenMaxTTL * int(time.Second)) + tm.accessTokenRefreshedTime = time.Now() + + tm.SetToken(response.AccessToken, accessTokenTTL, accessTokenMaxTTL) + + return nil +} + +// Fetches a new access token using client credentials +func (tm *MachineIdentityToken) FetchNewAccessToken() error { + + loginResponse, err := api.CallUniversalMachineIdentityLogin(api.MachineIdentityUniversalAuthLoginRequest{ + ClientId: tm.clientId, + ClientSecret: tm.clientSecret, + }) + if err != nil { + return err + } + + accessTokenTTL := time.Duration(loginResponse.ExpiresIn * int(time.Second)) + accessTokenMaxTTL := time.Duration(loginResponse.AccessTokenMaxTTL * int(time.Second)) + + if accessTokenTTL <= time.Duration(5)*time.Second { + fmt.Println("\nInfisical Authentication: At this time, k8 operator does not support refresh of tokens with 5 seconds or less ttl. Please increase access token ttl and try again") + os.Exit(1) + } + + tm.accessTokenFetchedTime = time.Now() + tm.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL) + + return nil +} + +func (tm *MachineIdentityToken) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) { + tm.mutex.Lock() + defer tm.mutex.Unlock() + + tm.accessToken = token + tm.accessTokenTTL = accessTokenTTL + tm.accessTokenMaxTTL = accessTokenMaxTTL +} + +func (tm *MachineIdentityToken) GetToken() (string, error) { + tm.mutex.Lock() + defer tm.mutex.Unlock() + + if tm.accessToken == "" { + return "", fmt.Errorf("no machine identity access token available") + } + + return tm.accessToken, nil +} From f6f6db2898121562c0f7321c44534c3e244eabac Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:21:51 +0100 Subject: [PATCH 11/31] Fix: Moved update attributes type to models --- k8-operator/packages/util/secrets.go | 81 +++++++++++++++++++++------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go index d3e13f536..3c9eca3e3 100644 --- a/k8-operator/packages/util/secrets.go +++ b/k8-operator/packages/util/secrets.go @@ -7,6 +7,7 @@ import ( "regexp" "strings" + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/crypto" "github.com/Infisical/infisical/k8-operator/packages/model" @@ -50,10 +51,44 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe return serviceTokenDetails, nil } -func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV3Response, error) { +func GetPlainTextSecretsViaUniversalAuthMachineIdentity(accessToken string, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { + + httpClient := resty.New() + httpClient.SetAuthScheme("Bearer") + httpClient.SetAuthToken(accessToken) + + secretsResponse, err := api.CallGetDecryptedSecretsV3(httpClient, api.GetDecryptedSecretsV3Request{ + ProjectID: secretScope.ProjectId, + Environment: secretScope.EnvSlug, + SecretPath: secretScope.SecretsPath, + ETag: etag, + }) + + if err != nil { + return nil, model.UpdateAttributes{}, err + } + + var secrets []model.SingleEnvironmentVariable + + for _, secret := range secretsResponse.Secrets { + secrets = append(secrets, model.SingleEnvironmentVariable{ + Key: secret.SecretKey, + Value: secret.SecretValue, + Type: secret.Type, + ID: secret.ID, + }) + } + + return secrets, model.UpdateAttributes{ + Modified: secretsResponse.Modified, + ETag: secretsResponse.ETag, + }, nil +} + +func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) if len(serviceTokenParts) < 4 { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again") + return nil, model.UpdateAttributes{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again") } serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2]) @@ -65,7 +100,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to get service token details. [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("unable to get service token details. [err=%v]", err) } encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{ @@ -76,51 +111,54 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en }) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, err + return nil, model.UpdateAttributes{}, err } decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err) } plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to decrypt the required workspace key") + return nil, model.UpdateAttributes{}, fmt.Errorf("unable to decrypt the required workspace key") } plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse.Secrets) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err) } plainTextSecretsMergedWithImports, err := InjectImportedSecret(plainTextWorkspaceKey, plainTextSecrets, encryptedSecretsResponse.ImportedSecrets) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, err + return nil, model.UpdateAttributes{}, err } // expand secrets that are referenced expandedSecrets := ExpandSecrets(plainTextSecretsMergedWithImports, fullServiceToken) - return expandedSecrets, encryptedSecretsResponse, nil + return expandedSecrets, model.UpdateAttributes{ + Modified: encryptedSecretsResponse.Modified, + ETag: encryptedSecretsResponse.ETag, + }, nil } // Fetches plaintext secrets from an API endpoint using a service account. // The function fetches the service account details and keys, decrypts the workspace key, fetches the encrypted secrets for the specified project and environment, and decrypts the secrets using the decrypted workspace key. // Returns the plaintext secrets, encrypted secrets response, and any errors that occurred during the process. -func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV3Response, error) { +func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { httpClient := resty.New() httpClient.SetAuthToken(serviceAccountCreds.AccessKey). SetHeader("Accept", "application/json") serviceAccountDetails, err := api.CallGetServiceTokenAccountDetailsV2(httpClient) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err) } serviceAccountKeys, err := api.CallGetServiceAccountKeysV2(httpClient, api.GetServiceAccountKeysRequest{ServiceAccountId: serviceAccountDetails.ServiceAccount.ID}) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err) } // find key for requested project @@ -132,28 +170,28 @@ func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccou } if workspaceServiceAccountKey.ID == "" || workspaceServiceAccountKey.EncryptedKey == "" || workspaceServiceAccountKey.Nonce == "" || serviceAccountCreds.PublicKey == "" || serviceAccountCreds.PrivateKey == "" { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err) + return nil, model.UpdateAttributes{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err) } cipherText, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.EncryptedKey) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode EncryptedKey secrets because [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode EncryptedKey secrets because [err=%v]", err) } nonce, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.Nonce) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode nonce secrets because [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode nonce secrets because [err=%v]", err) } publickey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PublicKey) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PublicKey secrets because [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PublicKey secrets because [err=%v]", err) } privateKey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PrivateKey) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PrivateKey secrets because [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PrivateKey secrets because [err=%v]", err) } plainTextWorkspaceKey := crypto.DecryptAsymmetric(cipherText, nonce, publickey, privateKey) @@ -165,15 +203,18 @@ func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccou }) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to fetch secrets because [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("unable to fetch secrets because [err=%v]", err) } plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse.Secrets) if err != nil { - return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err) + return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err) } - return plainTextSecrets, encryptedSecretsResponse, nil + return plainTextSecrets, model.UpdateAttributes{ + Modified: encryptedSecretsResponse.Modified, + ETag: encryptedSecretsResponse.ETag, + }, nil } func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) { From 71e31518d77d57fbccb30fcfc2761f4f2d41382e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:22:16 +0100 Subject: [PATCH 12/31] Feat: Add machine identity token handler --- k8-operator/packages/util/variables.go | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 k8-operator/packages/util/variables.go diff --git a/k8-operator/packages/util/variables.go b/k8-operator/packages/util/variables.go new file mode 100644 index 000000000..1c75569bf --- /dev/null +++ b/k8-operator/packages/util/variables.go @@ -0,0 +1,3 @@ +package util + +var MachineIdentityTokenInstance *MachineIdentityToken From 5c6781a70514507f9bbe4ca70b388f56bdcfb5e1 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:22:54 +0100 Subject: [PATCH 13/31] Update machine-identity-token.go --- .../packages/util/machine-identity-token.go | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/k8-operator/packages/util/machine-identity-token.go b/k8-operator/packages/util/machine-identity-token.go index 77c1ad0af..055e869ad 100644 --- a/k8-operator/packages/util/machine-identity-token.go +++ b/k8-operator/packages/util/machine-identity-token.go @@ -35,22 +35,22 @@ func NewMachineIdentityToken(clientId string, clientSecret string) *MachineIdent return &token } -func (tm *MachineIdentityToken) HandleTokenLifecycle() error { +func (t *MachineIdentityToken) HandleTokenLifecycle() error { for { - accessTokenMaxTTLExpiresInTime := tm.accessTokenFetchedTime.Add(tm.accessTokenMaxTTL - (5 * time.Second)) - accessTokenRefreshedTime := tm.accessTokenRefreshedTime + accessTokenMaxTTLExpiresInTime := t.accessTokenFetchedTime.Add(t.accessTokenMaxTTL - (5 * time.Second)) + accessTokenRefreshedTime := t.accessTokenRefreshedTime if accessTokenRefreshedTime.IsZero() { - accessTokenRefreshedTime = tm.accessTokenFetchedTime + accessTokenRefreshedTime = t.accessTokenFetchedTime } - nextAccessTokenExpiresInTime := accessTokenRefreshedTime.Add(tm.accessTokenTTL - (5 * time.Second)) + nextAccessTokenExpiresInTime := accessTokenRefreshedTime.Add(t.accessTokenTTL - (5 * time.Second)) - if tm.accessTokenFetchedTime.IsZero() && tm.accessTokenRefreshedTime.IsZero() { + if t.accessTokenFetchedTime.IsZero() && t.accessTokenRefreshedTime.IsZero() { // case: init login to get access token fmt.Println("\nInfisical Authentication: attempting to authenticate...") - err := tm.FetchNewAccessToken() + err := t.FetchNewAccessToken() if err != nil { fmt.Printf("\nInfisical Authentication: unable to authenticate universal auth because %v. Will retry in 30 seconds", err) @@ -60,7 +60,7 @@ func (tm *MachineIdentityToken) HandleTokenLifecycle() error { } } else if time.Now().After(accessTokenMaxTTLExpiresInTime) { fmt.Printf("\nInfisical Authentication: machine identity access token has reached max ttl, attempting to re authenticate...") - err := tm.FetchNewAccessToken() + err := t.FetchNewAccessToken() if err != nil { fmt.Printf("\nInfisical Authentication: unable to authenticate universal auth because %v. Will retry in 30 seconds", err) @@ -69,7 +69,7 @@ func (tm *MachineIdentityToken) HandleTokenLifecycle() error { continue } } else { - err := tm.RefreshAccessToken() + err := t.RefreshAccessToken() if err != nil { fmt.Printf("\nInfisical Authentication: unable to refresh universal auth token because %v. Will retry in 30 seconds", err) @@ -80,31 +80,31 @@ func (tm *MachineIdentityToken) HandleTokenLifecycle() error { } if accessTokenRefreshedTime.IsZero() { - accessTokenRefreshedTime = tm.accessTokenFetchedTime + accessTokenRefreshedTime = t.accessTokenFetchedTime } else { - accessTokenRefreshedTime = tm.accessTokenRefreshedTime + accessTokenRefreshedTime = t.accessTokenRefreshedTime } - nextAccessTokenExpiresInTime = accessTokenRefreshedTime.Add(tm.accessTokenTTL - (5 * time.Second)) - accessTokenMaxTTLExpiresInTime = tm.accessTokenFetchedTime.Add(tm.accessTokenMaxTTL - (5 * time.Second)) + nextAccessTokenExpiresInTime = accessTokenRefreshedTime.Add(t.accessTokenTTL - (5 * time.Second)) + accessTokenMaxTTLExpiresInTime = t.accessTokenFetchedTime.Add(t.accessTokenMaxTTL - (5 * time.Second)) if nextAccessTokenExpiresInTime.After(accessTokenMaxTTLExpiresInTime) { // case: Refreshed so close that the next refresh would occur beyond max ttl (this is because currently, token renew tries to add +access-token-ttl amount of time) // example: access token ttl is 11 sec and max ttl is 30 sec. So it will start with 11 seconds, then 22 seconds but the next time you call refresh it would try to extend it to 33 but max ttl only allows 30, so the token will be valid until 30 before we need to reauth - time.Sleep(tm.accessTokenTTL - nextAccessTokenExpiresInTime.Sub(accessTokenMaxTTLExpiresInTime)) + time.Sleep(t.accessTokenTTL - nextAccessTokenExpiresInTime.Sub(accessTokenMaxTTLExpiresInTime)) } else { - time.Sleep(tm.accessTokenTTL - (5 * time.Second)) + time.Sleep(t.accessTokenTTL - (5 * time.Second)) } } } -func (tm *MachineIdentityToken) RefreshAccessToken() error { +func (t *MachineIdentityToken) RefreshAccessToken() error { httpClient := resty.New() httpClient.SetRetryCount(10000). SetRetryMaxWaitTime(20 * time.Second). SetRetryWaitTime(5 * time.Second) - accessToken, err := tm.GetToken() + accessToken, err := t.GetToken() if err != nil { return err @@ -117,19 +117,19 @@ func (tm *MachineIdentityToken) RefreshAccessToken() error { accessTokenTTL := time.Duration(response.ExpiresIn * int(time.Second)) accessTokenMaxTTL := time.Duration(response.AccessTokenMaxTTL * int(time.Second)) - tm.accessTokenRefreshedTime = time.Now() + t.accessTokenRefreshedTime = time.Now() - tm.SetToken(response.AccessToken, accessTokenTTL, accessTokenMaxTTL) + t.SetToken(response.AccessToken, accessTokenTTL, accessTokenMaxTTL) return nil } // Fetches a new access token using client credentials -func (tm *MachineIdentityToken) FetchNewAccessToken() error { +func (t *MachineIdentityToken) FetchNewAccessToken() error { loginResponse, err := api.CallUniversalMachineIdentityLogin(api.MachineIdentityUniversalAuthLoginRequest{ - ClientId: tm.clientId, - ClientSecret: tm.clientSecret, + ClientId: t.clientId, + ClientSecret: t.clientSecret, }) if err != nil { return err @@ -143,28 +143,28 @@ func (tm *MachineIdentityToken) FetchNewAccessToken() error { os.Exit(1) } - tm.accessTokenFetchedTime = time.Now() - tm.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL) + t.accessTokenFetchedTime = time.Now() + t.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL) return nil } -func (tm *MachineIdentityToken) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) { - tm.mutex.Lock() - defer tm.mutex.Unlock() +func (t *MachineIdentityToken) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) { + t.mutex.Lock() + defer t.mutex.Unlock() - tm.accessToken = token - tm.accessTokenTTL = accessTokenTTL - tm.accessTokenMaxTTL = accessTokenMaxTTL + t.accessToken = token + t.accessTokenTTL = accessTokenTTL + t.accessTokenMaxTTL = accessTokenMaxTTL } -func (tm *MachineIdentityToken) GetToken() (string, error) { - tm.mutex.Lock() - defer tm.mutex.Unlock() +func (t *MachineIdentityToken) GetToken() (string, error) { + t.mutex.Lock() + defer t.mutex.Unlock() - if tm.accessToken == "" { + if t.accessToken == "" { return "", fmt.Errorf("no machine identity access token available") } - return tm.accessToken, nil + return t.accessToken, nil } From 33324a5a3c6e0ec75ad182e90a86d52dd8b96975 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 04:21:48 +0100 Subject: [PATCH 14/31] Type generation --- k8-operator/api/v1alpha1/infisicalsecret_types.go | 2 +- .../crd/bases/secrets.infisical.com_infisicalsecrets.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index ee2daed95..da799a41d 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -46,7 +46,7 @@ type MachineIdentityScopeInWorkspace struct { // +kubebuilder:validation:Required EnvSlug string `json:"envSlug"` // +kubebuilder:validation:Required - ProjectId string `json:"projectId"` + ProjectSlug string `json:"projectSlug"` } type KubeSecretReference struct { diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index f6d5b05a9..381e78acd 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -109,13 +109,13 @@ spec: properties: envSlug: type: string - projectId: + projectSlug: type: string secretsPath: type: string required: - envSlug - - projectId + - projectSlug - secretsPath type: object required: From f71459ede0214d4dd976062c291fc144621ec224 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 04:23:17 +0100 Subject: [PATCH 15/31] Slugs --- k8-operator/config/samples/sample.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml index 73240fafb..b2ad755eb 100644 --- a/k8-operator/config/samples/sample.yaml +++ b/k8-operator/config/samples/sample.yaml @@ -39,9 +39,9 @@ spec: universalAuthMachineIdentity: secretsScope: - projectId: "" # TODO: Make this a slug + projectSlug: envSlug: # "dev", "staging", "prod", etc.. - secretsPath: # Root is "/" + secretsPath: "" # Root is "/" credentials: secretName: universal-auth-credentials From 11edefa66faf28e39f77fe2dc54f7b9270ca19c7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 04:23:33 +0100 Subject: [PATCH 16/31] Feat: Added project slug support --- k8-operator/packages/api/api.go | 2 +- k8-operator/packages/api/models.go | 2 +- k8-operator/packages/util/secrets.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/k8-operator/packages/api/api.go b/k8-operator/packages/api/api.go index e69930ce1..05a58e8cb 100644 --- a/k8-operator/packages/api/api.go +++ b/k8-operator/packages/api/api.go @@ -153,7 +153,7 @@ func CallGetDecryptedSecretsV3(httpClient *resty.Client, request GetDecryptedSec SetResult(&decryptedSecretsResponse). SetHeader("User-Agent", USER_AGENT_NAME). SetQueryParam("secretPath", request.SecretPath). - SetQueryParam("workspaceId", request.ProjectID). + SetQueryParam("workspaceSlug", request.ProjectSlug). SetQueryParam("environment", request.Environment). Get(fmt.Sprintf("%v/v3/secrets/raw", API_HOST_URL)) diff --git a/k8-operator/packages/api/models.go b/k8-operator/packages/api/models.go index fa618791b..ff4ea3e10 100644 --- a/k8-operator/packages/api/models.go +++ b/k8-operator/packages/api/models.go @@ -97,7 +97,7 @@ type GetDecryptedSecretsV3Response struct { } type GetDecryptedSecretsV3Request struct { - ProjectID string `json:"projectId"` + ProjectSlug string `json:"workspaceSlug"` Environment string `json:"environment"` SecretPath string `json:"secretPath"` ETag string `json:"etag,omitempty"` diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go index 3c9eca3e3..6321e1582 100644 --- a/k8-operator/packages/util/secrets.go +++ b/k8-operator/packages/util/secrets.go @@ -58,7 +58,7 @@ func GetPlainTextSecretsViaUniversalAuthMachineIdentity(accessToken string, etag httpClient.SetAuthToken(accessToken) secretsResponse, err := api.CallGetDecryptedSecretsV3(httpClient, api.GetDecryptedSecretsV3Request{ - ProjectID: secretScope.ProjectId, + ProjectSlug: secretScope.ProjectSlug, Environment: secretScope.EnvSlug, SecretPath: secretScope.SecretsPath, ETag: etag, From ae3bc04b07e5876da8c2fcf39c93094e4ce77ca7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 7 Mar 2024 01:32:45 +0100 Subject: [PATCH 17/31] Docs --- docs/integrations/platforms/kubernetes.mdx | 72 ++++++++++++++++------ 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 7ddb616b2..49d6ce485 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -61,23 +61,45 @@ Once you have installed the operator to your cluster, you'll need to create a `I apiVersion: secrets.infisical.com/v1alpha1 kind: InfisicalSecret metadata: - # Name of of this InfisicalSecret resource - name: infisicalsecret-sample + name: infisicalsecret-sample + labels: + label-to-be-passed-to-managed-secret: sample-value + annotations: + example.com/annotation-to-be-passed-to-managed-secret: "sample-value" spec: - # The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used - hostAPI: https://app.infisical.com/api - resyncInterval: 60 - authentication: - serviceToken: - serviceTokenSecretReference: - secretName: service-token + hostAPI: https://app.infisical.com/api + resyncInterval: 10 + authentication: + # Make sure to only have 1 authentication method defined, serviceAccount/serviceToken/universalAuthMachineIdentity. + # If you have multiple authentication methods defined, it may cause issues. + universalAuthMachineIdentity: + secretsScope: + projectSlug: + envSlug: # "dev", "staging", "prod", etc.. + secretsPath: "" # Root is "/" + credentials: + secretName: universal-auth-credentials + secretNamespace: default + + serviceAccount: + serviceAccountSecretReference: + secretName: service-account + secretNamespace: default + projectId: "" + environmentName: "" + + serviceToken: + serviceTokenSecretReference: + secretName: service-token + secretNamespace: default + secretsScope: + envSlug: + secretsPath: # Root is "/" + + managedSecretReference: + secretName: managed-secret secretNamespace: default - secretsScope: - envSlug: dev - secretsPath: "/" - managedSecretReference: - secretName: managed-secret # <-- the name of kubernetes secret that will be created - secretNamespace: default # <-- where the kubernetes secret should be created + # secretType: kubernetes.io/dockerconfigjson ``` ### InfisicalSecret CRD properties @@ -105,11 +127,25 @@ Default re-sync interval is every 1 minute. - This block defines the method that will be used to authenticate with Infisical so that secrets can be fetched. Currently, only [Service Tokens](../../documentation/platform/token) can be used to authenticate with Infisical. + This block defines the method that will be used to authenticate with Infisical so that secrets can be fetched + + + +The universal machine identity authentication method is used to authenticate with Infisical. The client ID and client secret needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials. + +#### 1. Create a machine identity +You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth). + +#### 2. Create Kubernetes secret containing machine identity credentials + +``` bash + kubectl create secret generic universal-auth-credentials --from-literal=clientId="" --from-literal=clientSecret="" +``` + - The service token required to authenticate with Infisical needs to be stored in a Kubernetes secret. This block defines the reference to the name and name space of secret that stores this service token. + The service token required to authenticate with Infisical needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores this service token. Follow the instructions below to create and store the service token in a Kubernetes secrets and reference it in your CRD. #### 1. Generate service token @@ -122,7 +158,7 @@ Default re-sync interval is every 1 minute. To quickly create a Kubernetes secret containing the generated service token, you can run the command below. Make sure you replace `` with your service token. ``` bash - kubectl create secret generic service-token --from-literal=infisicalToken= + kubectl create secret generic service-token --from-literal=infisicalToken="" ``` #### 3. Add reference for the Kubernetes secret containing service token From b9601dd41843495d8cc1068032cd987e5a06163e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 7 Mar 2024 01:36:03 +0100 Subject: [PATCH 18/31] Update kubernetes.mdx --- docs/integrations/platforms/kubernetes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 49d6ce485..5f0a2a43e 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -12,7 +12,7 @@ The operator continuously updates secrets and can also reload dependent deployme ## Install Operator -The operator can be install via [Helm](helm.sh) or [kubectl](https://github.com/kubernetes/kubectl) +The operator can be install via [Helm](https://helm.sh) or [kubectl](https://github.com/kubernetes/kubectl) From 93bd3d8270304699f30c94fd5711409c81d19867 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 7 Mar 2024 01:48:38 +0100 Subject: [PATCH 19/31] Docs: Simplified docs more --- docs/integrations/platforms/kubernetes.mdx | 66 ++++++++++++++-------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 5f0a2a43e..d603c4a47 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -131,20 +131,48 @@ Default re-sync interval is every 1 minute. -The universal machine identity authentication method is used to authenticate with Infisical. The client ID and client secret needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials. + The universal machine identity authentication method is used to authenticate with Infisical. The client ID and client secret needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials. -#### 1. Create a machine identity -You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth). + #### 1. Create a machine identity + You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth). -#### 2. Create Kubernetes secret containing machine identity credentials + #### 2. Create Kubernetes secret containing machine identity credentials -``` bash - kubectl create secret generic universal-auth-credentials --from-literal=clientId="" --from-literal=clientSecret="" -``` + Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials. + To quickly create a Kubernetes secret containing the identity credentials, you can run the command below. Make sure you replace `` with the identity client ID and `` with the identity client secret. + ``` bash + kubectl create secret generic universal-auth-credentials --from-literal=clientId="" --from-literal=clientSecret="" + ``` + + #### 3. Add reference for the Kubernetes secret containing the identity credentials + Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceToken.universalAuthMachineIdentity` field in the InfisicalSecret resource. + + + Make sure to also populate the `secretsScope` field with the project slug _`projectSlug`_, environment slug _`envSlug`_, and secrets path _`secretsPath`_ that you want to fetch secrets from. Please see the example below. + + + ## Example + ```yaml + apiVersion: secrets.infisical.com/v1alpha1 + kind: InfisicalSecret + metadata: + name: infisicalsecret-sample-crd + spec: + authentication: + universalAuthMachineIdentity: + secretsScope: + projectSlug: # <-- project slug + envSlug: # "dev", "staging", "prod", etc.. + secretsPath: "" # Root is "/" + credentials: + secretName: universal-auth-credentials # <-- name of the Kubernetes secret that stores our machine identity credentials + secretNamespace: default # <-- namespace of the Kubernetes secret that stores our machine identity credentials + ... + ``` - + The service token required to authenticate with Infisical needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores this service token. Follow the instructions below to create and store the service token in a Kubernetes secrets and reference it in your CRD. @@ -165,6 +193,10 @@ You need to create a machine identity, and give it access to the project(s) you Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceToken.serviceTokenSecretReference` field in the InfisicalSecret resource. + + Make sure to also populate the `secretsScope` field with the, environment slug _`envSlug`_, and secrets path _`secretsPath`_ that you want to fetch secrets from. Please see the example below. + + ## Example ```yaml apiVersion: secrets.infisical.com/v1alpha1 @@ -177,25 +209,13 @@ You need to create a machine identity, and give it access to the project(s) you serviceTokenSecretReference: secretName: service-token # <-- name of the Kubernetes secret that stores our service token secretNamespace: option # <-- namespace of the Kubernetes secret that stores our service token + secretsScope: + envSlug: # "dev", "staging", "prod", etc.. + secretsPath: # Root is "/" ... ``` - - This block defines the scope of what secrets should be fetched. This is needed as your service token can have access to multiple folders and environments. - A scope is defined by `envSlug` and `secretsPath`. - - #### envSlug - - This refers to the short hand name of an environment. For example for the `development` environment the environment slug is `dev`. You can locate the slug of your environment by heading to your project settings in the Infisical dashboard. - - #### secretsPath - - secretsPath is the path to the secret in the given environment. For example a path of `/` would refer to the root of the environment whereas `/folder1` would refer to the secrets in folder1 from the root. - - Both fields are required. - - The `managedSecretReference` field is used to define the target location for storing secrets retrieved from an Infisical project. This field requires specifying both the name and namespace of the Kubernetes secret that will hold these secrets. From c5a11e839b1687a261a90faa95a06e77569a127e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:36:03 +0100 Subject: [PATCH 20/31] Feat: Deprecate Service Accounts --- k8-operator/config/samples/sample.yaml | 2 +- .../config/samples/serviceAccountCredsSecret.yaml | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 k8-operator/config/samples/serviceAccountCredsSecret.yaml diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml index b2ad755eb..17a804e99 100644 --- a/k8-operator/config/samples/sample.yaml +++ b/k8-operator/config/samples/sample.yaml @@ -37,7 +37,7 @@ spec: envSlug: secretsPath: # Root is "/" - universalAuthMachineIdentity: + universalAuth: secretsScope: projectSlug: envSlug: # "dev", "staging", "prod", etc.. diff --git a/k8-operator/config/samples/serviceAccountCredsSecret.yaml b/k8-operator/config/samples/serviceAccountCredsSecret.yaml deleted file mode 100644 index 859312ee2..000000000 --- a/k8-operator/config/samples/serviceAccountCredsSecret.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: service-account -type: Opaque -stringData: - serviceAccountAccessKey: <> - serviceAccountPrivateKey: <> - serviceAccountPublicKey: <> From 3765a14246c63c91471b56a30f1fe75789cbbf06 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:36:19 +0100 Subject: [PATCH 21/31] Fix: Generate new types --- k8-operator/api/v1alpha1/infisicalsecret_types.go | 4 ++-- k8-operator/api/v1alpha1/zz_generated.deepcopy.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index da799a41d..bcf969740 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -10,10 +10,10 @@ type Authentication struct { // +kubebuilder:validation:Optional ServiceToken ServiceTokenDetails `json:"serviceToken"` // +kubebuilder:validation:Optional - UniversalAuthMachineIdentity UniversalAuthMachineIdentityDetails `json:"universalAuthMachineIdentity"` + UniversalAuth UniversalAuthDetails `json:"universalAuth"` } -type UniversalAuthMachineIdentityDetails struct { +type UniversalAuthDetails struct { // +kubebuilder:validation:Required Credentials KubeSecretReference `json:"credentials"` // +kubebuilder:validation:Required diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index bab60dbfd..aa5cb1f51 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -31,7 +31,7 @@ func (in *Authentication) DeepCopyInto(out *Authentication) { *out = *in out.ServiceAccount = in.ServiceAccount out.ServiceToken = in.ServiceToken - out.UniversalAuthMachineIdentity = in.UniversalAuthMachineIdentity + out.UniversalAuth = in.UniversalAuth } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Authentication. @@ -237,18 +237,18 @@ func (in *ServiceTokenDetails) DeepCopy() *ServiceTokenDetails { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *UniversalAuthMachineIdentityDetails) DeepCopyInto(out *UniversalAuthMachineIdentityDetails) { +func (in *UniversalAuthDetails) DeepCopyInto(out *UniversalAuthDetails) { *out = *in out.Credentials = in.Credentials out.SecretsScope = in.SecretsScope } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UniversalAuthMachineIdentityDetails. -func (in *UniversalAuthMachineIdentityDetails) DeepCopy() *UniversalAuthMachineIdentityDetails { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UniversalAuthDetails. +func (in *UniversalAuthDetails) DeepCopy() *UniversalAuthDetails { if in == nil { return nil } - out := new(UniversalAuthMachineIdentityDetails) + out := new(UniversalAuthDetails) in.DeepCopyInto(out) return out } From c08c78de8de2825a6d9d65fa4ee449204cf645d8 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:36:51 +0100 Subject: [PATCH 22/31] Feat: Rename universalAuthMachineIdentity to universalAuth --- .../secrets.infisical.com_infisicalsecrets.yaml | 2 +- k8-operator/controllers/infisicalsecret_helper.go | 12 ++++++------ k8-operator/packages/util/secrets.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index 381e78acd..5e24d943a 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -90,7 +90,7 @@ spec: - secretsScope - serviceTokenSecretReference type: object - universalAuthMachineIdentity: + universalAuth: properties: credentials: properties: diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index 622d1fa5d..c719a6ea3 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -114,11 +114,11 @@ 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) { +func (r *InfisicalSecretReconciler) GetInfisicalUniversalAuthFromKubeSecret(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, + Namespace: infisicalSecret.Spec.Authentication.UniversalAuth.Credentials.SecretNamespace, + Name: infisicalSecret.Spec.Authentication.UniversalAuth.Credentials.SecretName, }) if errors.IsNotFound(err) { @@ -255,7 +255,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err) } - infisicalMachineIdentityCreds, err := r.GetInfisicalUniversalAuthMachineIdentityFromKubeSecret(ctx, infisicalSecret) + infisicalMachineIdentityCreds, err := r.GetInfisicalUniversalAuthFromKubeSecret(ctx, infisicalSecret) if err != nil { return fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err) } @@ -331,8 +331,8 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context return nil } - scope := infisicalSecret.Spec.Authentication.UniversalAuthMachineIdentity.SecretsScope - plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaUniversalAuthMachineIdentity(accessToken, secretVersionBasedOnETag, scope) + scope := infisicalSecret.Spec.Authentication.UniversalAuth.SecretsScope + plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaUniversalAuth(accessToken, secretVersionBasedOnETag, scope) fmt.Println("ReconcileInfisicalSecret: Fetched secrets via universal auth") if err != nil { diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go index 6321e1582..3bb8b672a 100644 --- a/k8-operator/packages/util/secrets.go +++ b/k8-operator/packages/util/secrets.go @@ -51,7 +51,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe return serviceTokenDetails, nil } -func GetPlainTextSecretsViaUniversalAuthMachineIdentity(accessToken string, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { +func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { httpClient := resty.New() httpClient.SetAuthScheme("Bearer") From caea055281402d1b5608441f3152f504b8c16d4f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:45:59 +0100 Subject: [PATCH 23/31] Feat: Improve K8 docs --- docs/integrations/platforms/kubernetes.mdx | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index d603c4a47..079830f99 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -70,9 +70,9 @@ spec: hostAPI: https://app.infisical.com/api resyncInterval: 10 authentication: - # Make sure to only have 1 authentication method defined, serviceAccount/serviceToken/universalAuthMachineIdentity. + # Make sure to only have 1 authentication method defined, serviceToken/universalAuth. # If you have multiple authentication methods defined, it may cause issues. - universalAuthMachineIdentity: + universalAuth: secretsScope: projectSlug: envSlug: # "dev", "staging", "prod", etc.. @@ -81,13 +81,6 @@ spec: secretName: universal-auth-credentials secretNamespace: default - serviceAccount: - serviceAccountSecretReference: - secretName: service-account - secretNamespace: default - projectId: "" - environmentName: "" - serviceToken: serviceTokenSecretReference: secretName: service-token @@ -130,23 +123,30 @@ Default re-sync interval is every 1 minute. This block defines the method that will be used to authenticate with Infisical so that secrets can be fetched - + The universal machine identity authentication method is used to authenticate with Infisical. The client ID and client secret needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials. - #### 1. Create a machine identity - You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth). + + + You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth). + + + Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials. + To quickly create a Kubernetes secret containing the identity credentials, you can run the command below. + + Make sure you replace `` with the identity client ID and `` with the identity client secret. - #### 2. Create Kubernetes secret containing machine identity credentials + ``` bash + kubectl create secret generic universal-auth-credentials --from-literal=clientId="" --from-literal=clientSecret="" + ``` + - Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials. - To quickly create a Kubernetes secret containing the identity credentials, you can run the command below. Make sure you replace `` with the identity client ID and `` with the identity client secret. + + Once the secret is created, add the `secretName` and `secretNamespace` of the secret that was just created under `authentication.universalAuth.credentials` field in the InfisicalSecret resource. + + - ``` bash - kubectl create secret generic universal-auth-credentials --from-literal=clientId="" --from-literal=clientSecret="" - ``` - #### 3. Add reference for the Kubernetes secret containing the identity credentials - Once the secret is created, add the name and namespace of the secret that was just created under `authentication.serviceToken.universalAuthMachineIdentity` field in the InfisicalSecret resource. Make sure to also populate the `secretsScope` field with the project slug _`projectSlug`_, environment slug _`envSlug`_, and secrets path _`secretsPath`_ that you want to fetch secrets from. Please see the example below. @@ -160,7 +160,7 @@ Default re-sync interval is every 1 minute. name: infisicalsecret-sample-crd spec: authentication: - universalAuthMachineIdentity: + universalAuth: secretsScope: projectSlug: # <-- project slug envSlug: # "dev", "staging", "prod", etc.. From 0341c32da0e552a56542de094614c11a9e172ed8 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:25:26 +0100 Subject: [PATCH 24/31] Fix: Change credentials -> credentialsRef --- docs/integrations/platforms/kubernetes.mdx | 6 +++--- k8-operator/api/v1alpha1/infisicalsecret_types.go | 2 +- k8-operator/api/v1alpha1/zz_generated.deepcopy.go | 2 +- .../crd/bases/secrets.infisical.com_infisicalsecrets.yaml | 4 ++-- k8-operator/config/samples/sample.yaml | 2 +- k8-operator/controllers/infisicalsecret_helper.go | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index 079830f99..29ca9c4ba 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -77,7 +77,7 @@ spec: projectSlug: envSlug: # "dev", "staging", "prod", etc.. secretsPath: "" # Root is "/" - credentials: + credentialsRef: secretName: universal-auth-credentials secretNamespace: default @@ -142,7 +142,7 @@ Default re-sync interval is every 1 minute. - Once the secret is created, add the `secretName` and `secretNamespace` of the secret that was just created under `authentication.universalAuth.credentials` field in the InfisicalSecret resource. + Once the secret is created, add the `secretName` and `secretNamespace` of the secret that was just created under `authentication.universalAuth.credentialsRef` field in the InfisicalSecret resource. @@ -165,7 +165,7 @@ Default re-sync interval is every 1 minute. projectSlug: # <-- project slug envSlug: # "dev", "staging", "prod", etc.. secretsPath: "" # Root is "/" - credentials: + credentialsRef: secretName: universal-auth-credentials # <-- name of the Kubernetes secret that stores our machine identity credentials secretNamespace: default # <-- namespace of the Kubernetes secret that stores our machine identity credentials ... diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index bcf969740..c29761b06 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -15,7 +15,7 @@ type Authentication struct { type UniversalAuthDetails struct { // +kubebuilder:validation:Required - Credentials KubeSecretReference `json:"credentials"` + CredentialsRef KubeSecretReference `json:"credentialsRef"` // +kubebuilder:validation:Required SecretsScope MachineIdentityScopeInWorkspace `json:"secretsScope"` } diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index aa5cb1f51..23251a194 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -239,7 +239,7 @@ func (in *ServiceTokenDetails) DeepCopy() *ServiceTokenDetails { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UniversalAuthDetails) DeepCopyInto(out *UniversalAuthDetails) { *out = *in - out.Credentials = in.Credentials + out.CredentialsRef = in.CredentialsRef out.SecretsScope = in.SecretsScope } diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index 5e24d943a..811eb9669 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -92,7 +92,7 @@ spec: type: object universalAuth: properties: - credentials: + credentialsRef: properties: secretName: description: The name of the Kubernetes Secret @@ -119,7 +119,7 @@ spec: - secretsPath type: object required: - - credentials + - credentialsRef - secretsScope type: object type: object diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml index 17a804e99..3b6a92846 100644 --- a/k8-operator/config/samples/sample.yaml +++ b/k8-operator/config/samples/sample.yaml @@ -43,7 +43,7 @@ spec: envSlug: # "dev", "staging", "prod", etc.. secretsPath: "" # Root is "/" - credentials: + credentialsRef: secretName: universal-auth-credentials secretNamespace: default diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index c719a6ea3..dc77525a0 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -117,8 +117,8 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context. func (r *InfisicalSecretReconciler) GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (machineIdentityDetails model.MachineIdentityDetails, err error) { universalAuthCredsFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ - Namespace: infisicalSecret.Spec.Authentication.UniversalAuth.Credentials.SecretNamespace, - Name: infisicalSecret.Spec.Authentication.UniversalAuth.Credentials.SecretName, + Namespace: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretNamespace, + Name: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretName, }) if errors.IsNotFound(err) { From 7bd4eed3289667cc7811d15e53954ebaa9c41e54 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:37:20 +0100 Subject: [PATCH 25/31] Chore: Generate K8 helm charts --- .../templates/infisicalsecret-crd.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml index 4ac32fca4..61e7a6719 100644 --- a/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml +++ b/helm-charts/secrets-operator/templates/infisicalsecret-crd.yaml @@ -90,6 +90,38 @@ spec: - secretsScope - serviceTokenSecretReference type: object + universalAuth: + properties: + credentialsRef: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret + is located + type: string + required: + - secretName + - secretNamespace + type: object + secretsScope: + properties: + envSlug: + type: string + projectSlug: + type: string + secretsPath: + type: string + required: + - envSlug + - projectSlug + - secretsPath + type: object + required: + - credentialsRef + - secretsScope + type: object type: object hostAPI: description: Infisical host to pull secrets from From d6b82dfaa4692e9d082cc829d273938652144efe Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:09:41 +0100 Subject: [PATCH 26/31] Fix: Rebase sample conflicts --- k8-operator/config/samples/sample.yaml | 38 ++++++++------------------ 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/k8-operator/config/samples/sample.yaml b/k8-operator/config/samples/sample.yaml index 3b6a92846..65347dec7 100644 --- a/k8-operator/config/samples/sample.yaml +++ b/k8-operator/config/samples/sample.yaml @@ -1,34 +1,17 @@ apiVersion: secrets.infisical.com/v1alpha1 kind: InfisicalSecret metadata: - name: infisicalsecret-sample - labels: - label-to-be-passed-to-managed-secret: sample-value - annotations: - reflector.v1.k8s.emberstack.com/reflection-allowed: 'true' + name: infisicalsecret-sample + labels: + label-to-be-passed-to-managed-secret: sample-value + annotations: + example.com/annotation-to-be-passed-to-managed-secret: "sample-value" spec: - hostAPI: http://localhost:8888/api - resyncInterval: 10 - authentication: - serviceAccount: - serviceAccountSecretReference: - secretName: service-account - secretNamespace: default - projectId: "6439ec224cfbf7ea2a95b651" - environmentName: "dev" - serviceToken: - serviceTokenSecretReference: - secretName: service-token - secretNamespace: default - secretsScope: - envSlug: dev - secretsPath: "/" - managedSecretReference: - secretName: managed-secret - secretNamespace: default - creationPolicy: "Orphan" ## Owner | Orphan - # secretType: kubernetes.io/dockerconfigjson - + hostAPI: https://app.infisical.com/api + resyncInterval: 10 + authentication: + # Make sure to only have 1 authentication method defined, serviceToken/universalAuth. + # If you have multiple authentication methods defined, it may cause issues. serviceToken: serviceTokenSecretReference: secretName: service-token @@ -50,6 +33,7 @@ spec: managedSecretReference: secretName: managed-secret secretNamespace: default + creationPolicy: "Orphan" ## Owner | Orphan # secretType: kubernetes.io/dockerconfigjson # # To be depreciated soon From 8cf68fbd9c32a8d1b5eacb954907e77482cc3248 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Wed, 6 Mar 2024 02:14:34 +0100 Subject: [PATCH 27/31] Generated --- ...ecrets.infisical.com_infisicalsecrets.yaml | 453 ++++++++---------- 1 file changed, 208 insertions(+), 245 deletions(-) diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index 811eb9669..513f1868b 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -2,251 +2,214 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: infisicalsecrets.secrets.infisical.com + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: infisicalsecrets.secrets.infisical.com spec: - group: secrets.infisical.com - names: - kind: InfisicalSecret - listKind: InfisicalSecretList - plural: infisicalsecrets - singular: infisicalsecret - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: InfisicalSecret is the Schema for the infisicalsecrets API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: InfisicalSecretSpec defines the desired state of InfisicalSecret - properties: - authentication: - properties: - serviceAccount: - properties: - environmentName: - type: string - projectId: - type: string - serviceAccountSecretReference: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret - is located - type: string - required: - - secretName - - secretNamespace - type: object - required: - - environmentName - - projectId - - serviceAccountSecretReference - type: object - serviceToken: - properties: - secretsScope: - properties: - envSlug: - type: string - secretsPath: - type: string - required: - - envSlug - - secretsPath - type: object - serviceTokenSecretReference: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret - is located - type: string - required: - - secretName - - secretNamespace - type: object - required: - - secretsScope - - serviceTokenSecretReference - type: object - universalAuth: - properties: - credentialsRef: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret - is located - type: string - required: - - secretName - - secretNamespace - type: object - secretsScope: - properties: - envSlug: - type: string - projectSlug: - type: string - secretsPath: - type: string - required: - - envSlug - - projectSlug - - secretsPath - type: object - required: - - credentialsRef - - secretsScope - type: object - type: object - hostAPI: - description: Infisical host to pull secrets from - type: string - managedSecretReference: - properties: - creationPolicy: - default: Orphan - description: 'The Kubernetes Secret creation policy. Enum with - values: ''Owner'', ''Orphan''. Owner creates the secret and - sets .metadata.ownerReferences of the InfisicalSecret CRD that - created it. Orphan will not set the secret owner. This will - result in the secret being orphaned and not deleted when the - resource is deleted.' - type: string - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret is located - type: string - secretType: - default: Opaque - description: 'The Kubernetes Secret type (experimental feature). - More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' - type: string - required: - - secretName - - secretNamespace - type: object - resyncInterval: - default: 60 - type: integer - tokenSecretReference: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret is located - type: string - required: - - secretName - - secretNamespace - type: object - required: - - managedSecretReference - - resyncInterval - type: object - status: - description: InfisicalSecretStatus defines the observed state of InfisicalSecret - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + group: secrets.infisical.com + names: + kind: InfisicalSecret + listKind: InfisicalSecretList + plural: infisicalsecrets + singular: infisicalsecret + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: InfisicalSecret is the Schema for the infisicalsecrets API properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type + apiVersion: + description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: InfisicalSecretSpec defines the desired state of InfisicalSecret + properties: + authentication: + properties: + serviceAccount: + properties: + environmentName: + type: string + projectId: + type: string + serviceAccountSecretReference: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - environmentName + - projectId + - serviceAccountSecretReference + type: object + serviceToken: + properties: + secretsScope: + properties: + envSlug: + type: string + secretsPath: + type: string + required: + - envSlug + - secretsPath + type: object + serviceTokenSecretReference: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - secretsScope + - serviceTokenSecretReference + type: object + universalAuth: + properties: + credentialsRef: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + secretsScope: + properties: + envSlug: + type: string + projectSlug: + type: string + secretsPath: + type: string + required: + - envSlug + - projectSlug + - secretsPath + type: object + required: + - credentialsRef + - secretsScope + type: object + type: object + hostAPI: + description: Infisical host to pull secrets from + type: string + managedSecretReference: + properties: + creationPolicy: + default: Orphan + description: "The Kubernetes Secret creation policy. Enum with values: 'Owner', 'Orphan'. Owner creates the secret and sets .metadata.ownerReferences of the InfisicalSecret CRD that created it. Orphan will not set the secret owner. This will result in the secret being orphaned and not deleted when the resource is deleted." + type: string + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + secretType: + default: Opaque + description: "The Kubernetes Secret type (experimental feature). More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" + type: string + required: + - secretName + - secretNamespace + type: object + resyncInterval: + default: 60 + type: integer + tokenSecretReference: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - managedSecretReference + - resyncInterval + type: object + status: + description: InfisicalSecretStatus defines the observed state of InfisicalSecret + properties: + conditions: + items: + description: + "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + required: + - conditions + type: object type: object - type: array - required: - - conditions - type: object - type: object - served: true - storage: true - subresources: - status: {} + served: true + storage: true + subresources: + status: {} From 18da522b453c3ef8b5aa0aed81a34e9b9aae292e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:35:00 +0100 Subject: [PATCH 28/31] Chore: Helm charts --- helm-charts/secrets-operator/values.yaml | 2 +- ...ecrets.infisical.com_infisicalsecrets.yaml | 453 ++++++++++-------- 2 files changed, 246 insertions(+), 209 deletions(-) diff --git a/helm-charts/secrets-operator/values.yaml b/helm-charts/secrets-operator/values.yaml index a5692b061..26f7a1938 100644 --- a/helm-charts/secrets-operator/values.yaml +++ b/helm-charts/secrets-operator/values.yaml @@ -32,7 +32,7 @@ controllerManager: - ALL image: repository: infisical/kubernetes-operator - tag: v0.4.0 # fixed to prevent accidental upgrade + tag: v0.5.0 # fixed to prevent accidental upgrade resources: limits: cpu: 500m diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml index 513f1868b..811eb9669 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalsecrets.yaml @@ -2,214 +2,251 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: infisicalsecrets.secrets.infisical.com + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: infisicalsecrets.secrets.infisical.com spec: - group: secrets.infisical.com - names: - kind: InfisicalSecret - listKind: InfisicalSecretList - plural: infisicalsecrets - singular: infisicalsecret - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: InfisicalSecret is the Schema for the infisicalsecrets API + group: secrets.infisical.com + names: + kind: InfisicalSecret + listKind: InfisicalSecretList + plural: infisicalsecrets + singular: infisicalsecret + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: InfisicalSecret is the Schema for the infisicalsecrets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: InfisicalSecretSpec defines the desired state of InfisicalSecret + properties: + authentication: + properties: + serviceAccount: + properties: + environmentName: + type: string + projectId: + type: string + serviceAccountSecretReference: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret + is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - environmentName + - projectId + - serviceAccountSecretReference + type: object + serviceToken: + properties: + secretsScope: + properties: + envSlug: + type: string + secretsPath: + type: string + required: + - envSlug + - secretsPath + type: object + serviceTokenSecretReference: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret + is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - secretsScope + - serviceTokenSecretReference + type: object + universalAuth: + properties: + credentialsRef: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret + is located + type: string + required: + - secretName + - secretNamespace + type: object + secretsScope: + properties: + envSlug: + type: string + projectSlug: + type: string + secretsPath: + type: string + required: + - envSlug + - projectSlug + - secretsPath + type: object + required: + - credentialsRef + - secretsScope + type: object + type: object + hostAPI: + description: Infisical host to pull secrets from + type: string + managedSecretReference: + properties: + creationPolicy: + default: Orphan + description: 'The Kubernetes Secret creation policy. Enum with + values: ''Owner'', ''Orphan''. Owner creates the secret and + sets .metadata.ownerReferences of the InfisicalSecret CRD that + created it. Orphan will not set the secret owner. This will + result in the secret being orphaned and not deleted when the + resource is deleted.' + type: string + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + secretType: + default: Opaque + description: 'The Kubernetes Secret type (experimental feature). + More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' + type: string + required: + - secretName + - secretNamespace + type: object + resyncInterval: + default: 60 + type: integer + tokenSecretReference: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - managedSecretReference + - resyncInterval + type: object + status: + description: InfisicalSecretStatus defines the observed state of InfisicalSecret + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" properties: - apiVersion: - description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" - type: string - kind: - description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" - type: string - metadata: - type: object - spec: - description: InfisicalSecretSpec defines the desired state of InfisicalSecret - properties: - authentication: - properties: - serviceAccount: - properties: - environmentName: - type: string - projectId: - type: string - serviceAccountSecretReference: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret is located - type: string - required: - - secretName - - secretNamespace - type: object - required: - - environmentName - - projectId - - serviceAccountSecretReference - type: object - serviceToken: - properties: - secretsScope: - properties: - envSlug: - type: string - secretsPath: - type: string - required: - - envSlug - - secretsPath - type: object - serviceTokenSecretReference: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret is located - type: string - required: - - secretName - - secretNamespace - type: object - required: - - secretsScope - - serviceTokenSecretReference - type: object - universalAuth: - properties: - credentialsRef: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret is located - type: string - required: - - secretName - - secretNamespace - type: object - secretsScope: - properties: - envSlug: - type: string - projectSlug: - type: string - secretsPath: - type: string - required: - - envSlug - - projectSlug - - secretsPath - type: object - required: - - credentialsRef - - secretsScope - type: object - type: object - hostAPI: - description: Infisical host to pull secrets from - type: string - managedSecretReference: - properties: - creationPolicy: - default: Orphan - description: "The Kubernetes Secret creation policy. Enum with values: 'Owner', 'Orphan'. Owner creates the secret and sets .metadata.ownerReferences of the InfisicalSecret CRD that created it. Orphan will not set the secret owner. This will result in the secret being orphaned and not deleted when the resource is deleted." - type: string - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret is located - type: string - secretType: - default: Opaque - description: "The Kubernetes Secret type (experimental feature). More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types" - type: string - required: - - secretName - - secretNamespace - type: object - resyncInterval: - default: 60 - type: integer - tokenSecretReference: - properties: - secretName: - description: The name of the Kubernetes Secret - type: string - secretNamespace: - description: The name space where the Kubernetes Secret is located - type: string - required: - - secretName - - secretNamespace - type: object - required: - - managedSecretReference - - resyncInterval - type: object - status: - description: InfisicalSecretStatus defines the observed state of InfisicalSecret - properties: - conditions: - items: - description: - "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - required: - - conditions - type: object + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type type: object - served: true - storage: true - subresources: - status: {} + type: array + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} From 08cfbf64e45b9ac4ad4ab754bccd5ba38cc73651 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:37:12 +0100 Subject: [PATCH 29/31] Fix: Error handing --- k8-operator/controllers/infisicalsecret_helper.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index dc77525a0..37d13592a 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -327,17 +327,16 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context accessToken, err := util.MachineIdentityTokenInstance.GetToken() if err != nil { - fmt.Println("\nReconcileInfisicalSecret: Waiting for access token to become available") - return nil + return fmt.Errorf("%s", "Waiting for access token to become available") } - scope := infisicalSecret.Spec.Authentication.UniversalAuth.SecretsScope plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaUniversalAuth(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) } + fmt.Println("ReconcileInfisicalSecret: Fetched secrets via universal auth") + } else { return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets") } From 02112ede07b299143aead241b632acb4621b75af Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:53:21 +0100 Subject: [PATCH 30/31] Fix: Requested changes --- k8-operator/controllers/infisicalsecret_helper.go | 13 +++++-------- k8-operator/packages/util/variables.go | 3 --- 2 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 k8-operator/packages/util/variables.go diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index 37d13592a..6141a75e7 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -40,6 +40,8 @@ var AuthStrategy = struct { UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY", } +var machineIdentityTokenInstance *util.MachineIdentityToken + func (r *InfisicalSecretReconciler) GetInfisicalConfigMap(ctx context.Context) (configMap map[string]string, errToReturn error) { // default key values defaultConfigMapData := make(map[string]string) @@ -291,16 +293,11 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION] } - if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY && util.MachineIdentityTokenInstance == nil { + if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY && machineIdentityTokenInstance == nil { // Create new machine identity token instance - util.MachineIdentityTokenInstance = util.NewMachineIdentityToken(infisicalMachineIdentityCreds.ClientId, infisicalMachineIdentityCreds.ClientSecret) + machineIdentityTokenInstance = util.NewMachineIdentityToken(infisicalMachineIdentityCreds.ClientId, infisicalMachineIdentityCreds.ClientSecret) } - // 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 model.UpdateAttributes @@ -324,7 +321,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service token") } else if authStrategy == AuthStrategy.UNIVERSAL_MACHINE_IDENTITY { // Machine Identity - accessToken, err := util.MachineIdentityTokenInstance.GetToken() + accessToken, err := machineIdentityTokenInstance.GetToken() if err != nil { return fmt.Errorf("%s", "Waiting for access token to become available") diff --git a/k8-operator/packages/util/variables.go b/k8-operator/packages/util/variables.go deleted file mode 100644 index 1c75569bf..000000000 --- a/k8-operator/packages/util/variables.go +++ /dev/null @@ -1,3 +0,0 @@ -package util - -var MachineIdentityTokenInstance *MachineIdentityToken From 91e3bbba34fcba4b26e7d9962d80651a9701ccb7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:58:10 +0100 Subject: [PATCH 31/31] Fix: Requested changes --- .../controllers/infisicalsecret_helper.go | 14 +++--- k8-operator/packages/model/model.go | 2 +- k8-operator/packages/util/secrets.go | 46 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret_helper.go index 6141a75e7..4ef378fe2 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret_helper.go @@ -299,10 +299,10 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context } var plainTextSecretsFromApi []model.SingleEnvironmentVariable - var updateAttributes model.UpdateAttributes + var updateDetails model.RequestUpdateUpdateDetails if authStrategy == AuthStrategy.SERVICE_ACCOUNT { // Service Account - plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaServiceAccount(serviceAccountCreds, infisicalSecret.Spec.Authentication.ServiceAccount.ProjectId, infisicalSecret.Spec.Authentication.ServiceAccount.EnvironmentName, secretVersionBasedOnETag) + plainTextSecretsFromApi, updateDetails, 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) } @@ -313,7 +313,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath - plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath) + plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath) if err != nil { return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) } @@ -327,7 +327,7 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context return fmt.Errorf("%s", "Waiting for access token to become available") } scope := infisicalSecret.Spec.Authentication.UniversalAuth.SecretsScope - plainTextSecretsFromApi, updateAttributes, err = util.GetPlainTextSecretsViaUniversalAuth(accessToken, secretVersionBasedOnETag, scope) + plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaUniversalAuth(accessToken, secretVersionBasedOnETag, scope) if err != nil { return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) @@ -338,15 +338,15 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context return fmt.Errorf("no authentication method provided. You must provide either a valid service token or a service account details to fetch secrets") } - if !updateAttributes.Modified { + if !updateDetails.Modified { fmt.Println("No secrets modified so reconcile not needed") return nil } if managedKubeSecret == nil { - return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateAttributes.ETag) + return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag) } else { - return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, updateAttributes.ETag) + return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag) } } diff --git a/k8-operator/packages/model/model.go b/k8-operator/packages/model/model.go index 17cbb9835..3d16f3a84 100644 --- a/k8-operator/packages/model/model.go +++ b/k8-operator/packages/model/model.go @@ -11,7 +11,7 @@ type MachineIdentityDetails struct { ClientSecret string } -type UpdateAttributes struct { +type RequestUpdateUpdateDetails struct { Modified bool ETag string } diff --git a/k8-operator/packages/util/secrets.go b/k8-operator/packages/util/secrets.go index 3bb8b672a..a8c4a4cc5 100644 --- a/k8-operator/packages/util/secrets.go +++ b/k8-operator/packages/util/secrets.go @@ -51,7 +51,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe return serviceTokenDetails, nil } -func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { +func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) { httpClient := resty.New() httpClient.SetAuthScheme("Bearer") @@ -65,7 +65,7 @@ func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secret }) if err != nil { - return nil, model.UpdateAttributes{}, err + return nil, model.RequestUpdateUpdateDetails{}, err } var secrets []model.SingleEnvironmentVariable @@ -79,16 +79,16 @@ func GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secret }) } - return secrets, model.UpdateAttributes{ + return secrets, model.RequestUpdateUpdateDetails{ Modified: secretsResponse.Modified, ETag: secretsResponse.ETag, }, nil } -func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { +func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) { serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) if len(serviceTokenParts) < 4 { - return nil, model.UpdateAttributes{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again") + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again") } serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2]) @@ -100,7 +100,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("unable to get service token details. [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to get service token details. [err=%v]", err) } encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{ @@ -111,33 +111,33 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en }) if err != nil { - return nil, model.UpdateAttributes{}, err + return nil, model.RequestUpdateUpdateDetails{}, err } decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err) } plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("unable to decrypt the required workspace key") + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to decrypt the required workspace key") } plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse.Secrets) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err) } plainTextSecretsMergedWithImports, err := InjectImportedSecret(plainTextWorkspaceKey, plainTextSecrets, encryptedSecretsResponse.ImportedSecrets) if err != nil { - return nil, model.UpdateAttributes{}, err + return nil, model.RequestUpdateUpdateDetails{}, err } // expand secrets that are referenced expandedSecrets := ExpandSecrets(plainTextSecretsMergedWithImports, fullServiceToken) - return expandedSecrets, model.UpdateAttributes{ + return expandedSecrets, model.RequestUpdateUpdateDetails{ Modified: encryptedSecretsResponse.Modified, ETag: encryptedSecretsResponse.ETag, }, nil @@ -146,19 +146,19 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, en // Fetches plaintext secrets from an API endpoint using a service account. // The function fetches the service account details and keys, decrypts the workspace key, fetches the encrypted secrets for the specified project and environment, and decrypts the secrets using the decrypted workspace key. // Returns the plaintext secrets, encrypted secrets response, and any errors that occurred during the process. -func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, model.UpdateAttributes, error) { +func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccountDetails, projectId string, environmentName string, etag string) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) { httpClient := resty.New() httpClient.SetAuthToken(serviceAccountCreds.AccessKey). SetHeader("Accept", "application/json") serviceAccountDetails, err := api.CallGetServiceTokenAccountDetailsV2(httpClient) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account details. [err=%v]", err) } serviceAccountKeys, err := api.CallGetServiceAccountKeysV2(httpClient, api.GetServiceAccountKeysRequest{ServiceAccountId: serviceAccountDetails.ServiceAccount.ID}) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get service account key details. [err=%v]", err) } // find key for requested project @@ -170,28 +170,28 @@ func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccou } if workspaceServiceAccountKey.ID == "" || workspaceServiceAccountKey.EncryptedKey == "" || workspaceServiceAccountKey.Nonce == "" || serviceAccountCreds.PublicKey == "" || serviceAccountCreds.PrivateKey == "" { - return nil, model.UpdateAttributes{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to find key for [projectId=%s] [err=%v]. Ensure that the given service account has access to given projectId", projectId, err) } cipherText, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.EncryptedKey) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode EncryptedKey secrets because [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode EncryptedKey secrets because [err=%v]", err) } nonce, err := base64.StdEncoding.DecodeString(workspaceServiceAccountKey.Nonce) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode nonce secrets because [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode nonce secrets because [err=%v]", err) } publickey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PublicKey) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PublicKey secrets because [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PublicKey secrets because [err=%v]", err) } privateKey, err := base64.StdEncoding.DecodeString(serviceAccountCreds.PrivateKey) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PrivateKey secrets because [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to decode PrivateKey secrets because [err=%v]", err) } plainTextWorkspaceKey := crypto.DecryptAsymmetric(cipherText, nonce, publickey, privateKey) @@ -203,15 +203,15 @@ func GetPlainTextSecretsViaServiceAccount(serviceAccountCreds model.ServiceAccou }) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("unable to fetch secrets because [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("unable to fetch secrets because [err=%v]", err) } plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecretsResponse.Secrets) if err != nil { - return nil, model.UpdateAttributes{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err) + return nil, model.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err) } - return plainTextSecrets, model.UpdateAttributes{ + return plainTextSecrets, model.RequestUpdateUpdateDetails{ Modified: encryptedSecretsResponse.Modified, ETag: encryptedSecretsResponse.ETag, }, nil