add secret reference support

This commit is contained in:
Maidul Islam
2023-07-07 15:49:21 -04:00
parent 8a237af4ac
commit f82fa1b3b3
6 changed files with 167 additions and 16 deletions

View File

@@ -4,8 +4,19 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type Authentication struct {
// +kubebuilder:validation:Optional
ServiceAccount ServiceAccountDetails `json:"serviceAccount"`
// +kubebuilder:validation:Optional
ServiceToken ServiceTokenDetails `json:"serviceToken"`
}
type ServiceTokenDetails struct { type ServiceTokenDetails struct {
// +kubebuilder:validation:Required
ServiceTokenSecretReference KubeSecretReference `json:"serviceTokenSecretReference"` ServiceTokenSecretReference KubeSecretReference `json:"serviceTokenSecretReference"`
// +kubebuilder:validation:Required
SecretsScope SecretScopeInWorkspace `json:"secretsScope"`
} }
type ServiceAccountDetails struct { type ServiceAccountDetails struct {
@@ -14,11 +25,12 @@ type ServiceAccountDetails struct {
EnvironmentName string `json:"environmentName"` EnvironmentName string `json:"environmentName"`
} }
type Authentication struct { type SecretScopeInWorkspace struct {
// +kubebuilder:validation:Optional // +kubebuilder:validation:Required
ServiceAccount ServiceAccountDetails `json:"serviceAccount"` SecretsPath string `json:"secretsPath"`
// +kubebuilder:validation:Optional
ServiceToken ServiceTokenDetails `json:"serviceToken"` // +kubebuilder:validation:Required
EnvSlug string `json:"envSlug"`
} }
type KubeSecretReference struct { type KubeSecretReference struct {

View File

@@ -157,6 +157,21 @@ func (in *KubeSecretReference) DeepCopy() *KubeSecretReference {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretScopeInWorkspace) DeepCopyInto(out *SecretScopeInWorkspace) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretScopeInWorkspace.
func (in *SecretScopeInWorkspace) DeepCopy() *SecretScopeInWorkspace {
if in == nil {
return nil
}
out := new(SecretScopeInWorkspace)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceAccountDetails) DeepCopyInto(out *ServiceAccountDetails) { func (in *ServiceAccountDetails) DeepCopyInto(out *ServiceAccountDetails) {
*out = *in *out = *in
@@ -177,6 +192,7 @@ func (in *ServiceAccountDetails) DeepCopy() *ServiceAccountDetails {
func (in *ServiceTokenDetails) DeepCopyInto(out *ServiceTokenDetails) { func (in *ServiceTokenDetails) DeepCopyInto(out *ServiceTokenDetails) {
*out = *in *out = *in
out.ServiceTokenSecretReference = in.ServiceTokenSecretReference out.ServiceTokenSecretReference = in.ServiceTokenSecretReference
out.SecretsScope = in.SecretsScope
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTokenDetails. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTokenDetails.

View File

@@ -63,6 +63,16 @@ spec:
type: object type: object
serviceToken: serviceToken:
properties: properties:
secretsScope:
properties:
envSlug:
type: string
secretsPath:
type: string
required:
- envSlug
- secretsPath
type: object
serviceTokenSecretReference: serviceTokenSecretReference:
properties: properties:
secretName: secretName:
@@ -77,6 +87,7 @@ spec:
- secretNamespace - secretNamespace
type: object type: object
required: required:
- secretsScope
- serviceTokenSecretReference - serviceTokenSecretReference
type: object type: object
type: object type: object

View File

@@ -3,8 +3,8 @@ kind: InfisicalSecret
metadata: metadata:
name: infisicalsecret-sample name: infisicalsecret-sample
spec: spec:
hostAPI: http://localhost:7070/api hostAPI: http://localhost:8764/api
resyncInterval: 60 resyncInterval: 10
authentication: authentication:
serviceAccount: serviceAccount:
serviceAccountSecretReference: serviceAccountSecretReference:
@@ -16,10 +16,13 @@ spec:
serviceTokenSecretReference: serviceTokenSecretReference:
secretName: service-token secretName: service-token
secretNamespace: default secretNamespace: default
secretsScope:
envSlug: dev
secretsPath: "/"
managedSecretReference: managedSecretReference:
secretName: managed-secret secretName: managed-secret
secretNamespace: default secretNamespace: default
# To be depreciated soon # # To be depreciated soon
tokenSecretReference: # tokenSecretReference:
secretName: service-token # secretName: service-token
secretNamespace: default # secretNamespace: default

View File

@@ -219,7 +219,10 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account") fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account")
} else if infisicalToken != "" { } else if infisicalToken != "" {
plainTextSecretsFromApi, fullEncryptedSecretsResponse, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag) envSlug := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.EnvSlug
secretsPath := infisicalSecret.Spec.Authentication.ServiceToken.SecretsScope.SecretsPath
plainTextSecretsFromApi, fullEncryptedSecretsResponse, err = util.GetPlainTextSecretsViaServiceToken(infisicalToken, secretVersionBasedOnETag, envSlug, secretsPath)
if err != nil { if err != nil {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
} }

View File

@@ -3,6 +3,8 @@ package util
import ( import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"path"
"regexp"
"strings" "strings"
"github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/api"
@@ -48,7 +50,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe
return serviceTokenDetails, nil return serviceTokenDetails, nil
} }
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV3Response, error) { func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string, envSlug string, secretPath string) ([]model.SingleEnvironmentVariable, api.GetEncryptedSecretsV3Response, error) {
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4) serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
if len(serviceTokenParts) < 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, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
@@ -68,9 +70,9 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([
encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{ encryptedSecretsResponse, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
WorkspaceId: serviceTokenDetails.Workspace, WorkspaceId: serviceTokenDetails.Workspace,
Environment: serviceTokenDetails.Environment, Environment: envSlug,
ETag: etag, ETag: etag,
SecretPath: serviceTokenDetails.SecretPath, SecretPath: secretPath,
}) })
if err != nil { if err != nil {
@@ -92,7 +94,10 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, etag string) ([
return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err) return nil, api.GetEncryptedSecretsV3Response{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
} }
return plainTextSecrets, encryptedSecretsResponse, nil // expand secrets that are referenced
expandedSecrets := ExpandSecrets(plainTextSecrets, fullServiceToken)
return expandedSecrets, encryptedSecretsResponse, nil
} }
// Fetches plaintext secrets from an API endpoint using a service account. // Fetches plaintext secrets from an API endpoint using a service account.
@@ -252,3 +257,104 @@ func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSe
return plainTextSecrets, nil return plainTextSecrets, nil
} }
var secRefRegex = regexp.MustCompile(`\${([^\}]*)}`)
func recursivelyExpandSecret(expandedSecs map[string]string, interpolatedSecs map[string]string, crossSecRefFetch func(env string, path []string, key string) string, key string) string {
if v, ok := expandedSecs[key]; ok {
return v
}
interpolatedVal, ok := interpolatedSecs[key]
if !ok {
return ""
// panic(fmt.Errorf("Could not find referred secret with key name %s", key), "Please check it refers a")
}
refs := secRefRegex.FindAllStringSubmatch(interpolatedVal, -1)
for _, val := range refs {
// key: "${something}" val: [${something},something]
interpolatedExp, interpolationKey := val[0], val[1]
ref := strings.Split(interpolationKey, ".")
// ${KEY1} => [key1]
if len(ref) == 1 {
val := recursivelyExpandSecret(expandedSecs, interpolatedSecs, crossSecRefFetch, interpolationKey)
interpolatedVal = strings.ReplaceAll(interpolatedVal, interpolatedExp, val)
continue
}
// cross board reference ${env.folder.key1} => [env folder key1]
if len(ref) > 1 {
secEnv, tmpSecPath, secKey := ref[0], ref[1:len(ref)-1], ref[len(ref)-1]
interpolatedSecs[interpolationKey] = crossSecRefFetch(secEnv, tmpSecPath, secKey) // get the reference value
val := recursivelyExpandSecret(expandedSecs, interpolatedSecs, crossSecRefFetch, interpolationKey)
interpolatedVal = strings.ReplaceAll(interpolatedVal, interpolatedExp, val)
}
}
expandedSecs[key] = interpolatedVal
return interpolatedVal
}
func ExpandSecrets(secrets []model.SingleEnvironmentVariable, infisicalToken string) []model.SingleEnvironmentVariable {
expandedSecs := make(map[string]string)
interpolatedSecs := make(map[string]string)
// map[env.secret-path][keyname]Secret
crossEnvRefSecs := make(map[string]map[string]model.SingleEnvironmentVariable) // a cache to hold all cross board reference secrets
for _, sec := range secrets {
// get all references in a secret
refs := secRefRegex.FindAllStringSubmatch(sec.Value, -1)
// nil means its a secret without reference
if refs == nil {
expandedSecs[sec.Key] = sec.Value // atomic secrets without any interpolation
} else {
interpolatedSecs[sec.Key] = sec.Value
}
}
for i, sec := range secrets {
// already present pick that up
if expandedVal, ok := expandedSecs[sec.Key]; ok {
secrets[i].Value = expandedVal
continue
}
expandedVal := recursivelyExpandSecret(expandedSecs, interpolatedSecs, func(env string, secPaths []string, secKey string) string {
secPaths = append([]string{"/"}, secPaths...)
secPath := path.Join(secPaths...)
secPathDot := strings.Join(secPaths, ".")
uniqKey := fmt.Sprintf("%s.%s", env, secPathDot)
if crossRefSec, ok := crossEnvRefSecs[uniqKey]; !ok {
// if not in cross reference cache, fetch it from server
refSecs, _, err := GetPlainTextSecretsViaServiceToken(infisicalToken, "", env, secPath)
if err != nil {
fmt.Println("HELLO===>", "MOO", err)
// HandleError(err, fmt.Sprintf("Could not fetch secrets in environment: %s secret-path: %s", env, secPath), "If you are using a service token to fetch secrets, please ensure it is valid")
}
refSecsByKey := getSecretsByKeys(refSecs)
// save it to avoid calling api again for same environment and folder path
crossEnvRefSecs[uniqKey] = refSecsByKey
return refSecsByKey[secKey].Value
} else {
return crossRefSec[secKey].Value
}
}, sec.Key)
secrets[i].Value = expandedVal
}
return secrets
}
func getSecretsByKeys(secrets []model.SingleEnvironmentVariable) map[string]model.SingleEnvironmentVariable {
secretMapByName := make(map[string]model.SingleEnvironmentVariable, len(secrets))
for _, secret := range secrets {
secretMapByName[secret.Key] = secret
}
return secretMapByName
}