Merge pull request #1533 from Infisical/daniel/k8-operator-machine-identities

Feat: K8 Operator Machine Identity Support
This commit is contained in:
Maidul Islam
2024-03-21 15:01:50 -04:00
committed by GitHub
16 changed files with 699 additions and 124 deletions

View File

@@ -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)
<Tabs>
<Tab title="Helm (recommended)">
@@ -61,23 +61,38 @@ 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, serviceToken/universalAuth.
# If you have multiple authentication methods defined, it may cause issues.
universalAuth:
secretsScope:
projectSlug: <project-slug>
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
credentialsRef:
secretName: universal-auth-credentials
secretNamespace: default
serviceToken:
serviceTokenSecretReference:
secretName: service-token
secretNamespace: default
secretsScope:
envSlug: <env-slug>
secretsPath: <secrets-path> # 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 +120,60 @@ Default re-sync interval is every 1 minute.
</Accordion>
<Accordion title="authentication">
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
</Accordion>
<Accordion title="authentication.serviceToken.serviceTokenSecretReference">
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.
<Accordion title="authentication.universalAuth">
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.
<Steps>
<Step title="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).
</Step>
<Step title="Create Kubernetes secret containing machine identity credentials">
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 `<your-identity-client-id>` with the identity client ID and `<your-identity-client-secret>` with the identity client secret.
``` bash
kubectl create secret generic universal-auth-credentials --from-literal=clientId="<your-identity-client-id>" --from-literal=clientSecret="<your-identity-client-secret>"
```
</Step>
<Step title="Add reference for the Kubernetes secret containing the identity credentials">
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.
</Step>
</Steps>
<Info>
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.
</Info>
## Example
```yaml
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
name: infisicalsecret-sample-crd
spec:
authentication:
universalAuth:
secretsScope:
projectSlug: <project-slug> # <-- project slug
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
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
...
```
</Accordion>
<Accordion title="authentication.serviceToken">
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,13 +186,17 @@ 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 `<your-service-token-here>` with your service token.
``` bash
kubectl create secret generic service-token --from-literal=infisicalToken=<your-service-token-here>
kubectl create secret generic service-token --from-literal=infisicalToken="<your-service-token-here>"
```
#### 3. Add reference for the Kubernetes secret containing service token
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.
<Info>
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.
</Info>
## Example
```yaml
apiVersion: secrets.infisical.com/v1alpha1
@@ -141,25 +209,13 @@ Default re-sync interval is every 1 minute.
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: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: <secrets-path> # Root is "/"
...
```
</Accordion>
<Accordion title="authentication.serviceToken.secretsScope">
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.
</Accordion>
<Accordion title="managedSecretReference">
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.

View File

@@ -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

View File

@@ -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

View File

@@ -9,12 +9,20 @@ type Authentication struct {
ServiceAccount ServiceAccountDetails `json:"serviceAccount"`
// +kubebuilder:validation:Optional
ServiceToken ServiceTokenDetails `json:"serviceToken"`
// +kubebuilder:validation:Optional
UniversalAuth UniversalAuthDetails `json:"universalAuth"`
}
type UniversalAuthDetails struct {
// +kubebuilder:validation:Required
CredentialsRef KubeSecretReference `json:"credentialsRef"`
// +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
ProjectSlug string `json:"projectSlug"`
}
type KubeSecretReference struct {
// The name of the Kubernetes Secret
// +kubebuilder:validation:Required

View File

@@ -31,6 +31,7 @@ func (in *Authentication) DeepCopyInto(out *Authentication) {
*out = *in
out.ServiceAccount = in.ServiceAccount
out.ServiceToken = in.ServiceToken
out.UniversalAuth = in.UniversalAuth
}
// 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 *UniversalAuthDetails) DeepCopyInto(out *UniversalAuthDetails) {
*out = *in
out.CredentialsRef = in.CredentialsRef
out.SecretsScope = in.SecretsScope
}
// 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(UniversalAuthDetails)
in.DeepCopyInto(out)
return out
}

View File

@@ -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

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: universal-auth-credentials
type: Opaque
stringData:
clientId: <machine-identity-client-id>
clientSecret: <machine-identity-client-secret>

View File

@@ -1,35 +1,42 @@
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
secretNamespace: default
secretsScope:
envSlug: <env-slug>
secretsPath: <secrets-path> # Root is "/"
# # To be depreciated soon
# tokenSecretReference:
# secretName: service-token
# secretNamespace: default
universalAuth:
secretsScope:
projectSlug: <project-slug>
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
credentialsRef:
secretName: universal-auth-credentials
secretNamespace: default
managedSecretReference:
secretName: managed-secret
secretNamespace: default
creationPolicy: "Orphan" ## Owner | Orphan
# secretType: kubernetes.io/dockerconfigjson
# # To be depreciated soon
# tokenSecretReference:
# secretName: service-token
# secretNamespace: default

View File

@@ -1,9 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: service-account
type: Opaque
stringData:
serviceAccountAccessKey: <>
serviceAccountPrivateKey: <>
serviceAccountPublicKey: <>

View File

@@ -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{

View File

@@ -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"
@@ -20,12 +19,29 @@ 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",
}
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)
@@ -100,6 +116,28 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.
return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil
}
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.CredentialsRef.SecretNamespace,
Name: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.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 +165,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 +194,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 +225,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 +250,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.GetInfisicalUniversalAuthFromKubeSecret(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 +293,60 @@ 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 && machineIdentityTokenInstance == nil {
// Create new machine identity token instance
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)
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
var updateDetails model.RequestUpdateUpdateDetails
if authStrategy == AuthStrategy.SERVICE_ACCOUNT { // Service Account
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)
}
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, updateDetails, 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 := machineIdentityTokenInstance.GetToken()
if err != nil {
return fmt.Errorf("%s", "Waiting for access token to become available")
}
scope := infisicalSecret.Spec.Authentication.UniversalAuth.SecretsScope
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaUniversalAuth(accessToken, secretVersionBasedOnETag, scope)
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")
}
if !fullEncryptedSecretsResponse.Modified {
fmt.Println("No secrets modified so reconcile not needed", "Etag:", fullEncryptedSecretsResponse.ETag, "Modified:", fullEncryptedSecretsResponse.Modified)
if !updateDetails.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, updateDetails.ETag)
} else {
return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, fullEncryptedSecretsResponse)
return r.UpdateInfisicalManagedKubeSecret(ctx, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag)
}
}

View File

@@ -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("workspaceSlug", request.ProjectSlug).
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.

View File

@@ -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 {
ProjectSlug string `json:"workspaceSlug"`
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"`

View File

@@ -6,6 +6,16 @@ type ServiceAccountDetails struct {
PrivateKey string
}
type MachineIdentityDetails struct {
ClientId string
ClientSecret string
}
type RequestUpdateUpdateDetails struct {
Modified bool
ETag string
}
type SingleEnvironmentVariable struct {
Key string `json:"key"`
Value string `json:"value"`

View File

@@ -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 (t *MachineIdentityToken) HandleTokenLifecycle() error {
for {
accessTokenMaxTTLExpiresInTime := t.accessTokenFetchedTime.Add(t.accessTokenMaxTTL - (5 * time.Second))
accessTokenRefreshedTime := t.accessTokenRefreshedTime
if accessTokenRefreshedTime.IsZero() {
accessTokenRefreshedTime = t.accessTokenFetchedTime
}
nextAccessTokenExpiresInTime := accessTokenRefreshedTime.Add(t.accessTokenTTL - (5 * time.Second))
if t.accessTokenFetchedTime.IsZero() && t.accessTokenRefreshedTime.IsZero() {
// case: init login to get access token
fmt.Println("\nInfisical Authentication: attempting to authenticate...")
err := t.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 := t.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 := t.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 = t.accessTokenFetchedTime
} else {
accessTokenRefreshedTime = t.accessTokenRefreshedTime
}
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(t.accessTokenTTL - nextAccessTokenExpiresInTime.Sub(accessTokenMaxTTLExpiresInTime))
} else {
time.Sleep(t.accessTokenTTL - (5 * time.Second))
}
}
}
func (t *MachineIdentityToken) RefreshAccessToken() error {
httpClient := resty.New()
httpClient.SetRetryCount(10000).
SetRetryMaxWaitTime(20 * time.Second).
SetRetryWaitTime(5 * time.Second)
accessToken, err := t.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))
t.accessTokenRefreshedTime = time.Now()
t.SetToken(response.AccessToken, accessTokenTTL, accessTokenMaxTTL)
return nil
}
// Fetches a new access token using client credentials
func (t *MachineIdentityToken) FetchNewAccessToken() error {
loginResponse, err := api.CallUniversalMachineIdentityLogin(api.MachineIdentityUniversalAuthLoginRequest{
ClientId: t.clientId,
ClientSecret: t.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)
}
t.accessTokenFetchedTime = time.Now()
t.SetToken(loginResponse.AccessToken, accessTokenTTL, accessTokenMaxTTL)
return nil
}
func (t *MachineIdentityToken) SetToken(token string, accessTokenTTL time.Duration, accessTokenMaxTTL time.Duration) {
t.mutex.Lock()
defer t.mutex.Unlock()
t.accessToken = token
t.accessTokenTTL = accessTokenTTL
t.accessTokenMaxTTL = accessTokenMaxTTL
}
func (t *MachineIdentityToken) GetToken() (string, error) {
t.mutex.Lock()
defer t.mutex.Unlock()
if t.accessToken == "" {
return "", fmt.Errorf("no machine identity access token available")
}
return t.accessToken, nil
}

View File

@@ -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 GetPlainTextSecretsViaUniversalAuth(accessToken string, etag string, secretScope v1alpha1.MachineIdentityScopeInWorkspace) ([]model.SingleEnvironmentVariable, model.RequestUpdateUpdateDetails, error) {
httpClient := resty.New()
httpClient.SetAuthScheme("Bearer")
httpClient.SetAuthToken(accessToken)
secretsResponse, err := api.CallGetDecryptedSecretsV3(httpClient, api.GetDecryptedSecretsV3Request{
ProjectSlug: secretScope.ProjectSlug,
Environment: secretScope.EnvSlug,
SecretPath: secretScope.SecretsPath,
ETag: etag,
})
if err != nil {
return nil, model.RequestUpdateUpdateDetails{}, 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.RequestUpdateUpdateDetails{
Modified: secretsResponse.Modified,
ETag: secretsResponse.ETag,
}, nil
}
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, api.GetEncryptedSecretsV3Response{}, 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])
@@ -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.RequestUpdateUpdateDetails{}, 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.RequestUpdateUpdateDetails{}, 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.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, api.GetEncryptedSecretsV3Response{}, 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, api.GetEncryptedSecretsV3Response{}, 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, api.GetEncryptedSecretsV3Response{}, err
return nil, model.RequestUpdateUpdateDetails{}, err
}
// expand secrets that are referenced
expandedSecrets := ExpandSecrets(plainTextSecretsMergedWithImports, fullServiceToken)
return expandedSecrets, encryptedSecretsResponse, nil
return expandedSecrets, model.RequestUpdateUpdateDetails{
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.RequestUpdateUpdateDetails, 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.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, api.GetEncryptedSecretsV3Response{}, 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
@@ -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.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, api.GetEncryptedSecretsV3Response{}, 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, api.GetEncryptedSecretsV3Response{}, 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, api.GetEncryptedSecretsV3Response{}, 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, api.GetEncryptedSecretsV3Response{}, 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)
@@ -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.RequestUpdateUpdateDetails{}, 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.RequestUpdateUpdateDetails{}, fmt.Errorf("GetPlainTextSecretsViaServiceAccount: unable to get plain text secrets because [err=%v]", err)
}
return plainTextSecrets, encryptedSecretsResponse, nil
return plainTextSecrets, model.RequestUpdateUpdateDetails{
Modified: encryptedSecretsResponse.Modified,
ETag: encryptedSecretsResponse.ETag,
}, nil
}
func GetBase64DecodedSymmetricEncryptionDetails(key string, cipher string, IV string, tag string) (DecodedSymmetricEncryptionDetails, error) {