add secret reference support

pull/713/head
Maidul Islam 11 months ago
parent 8a237af4ac
commit f82fa1b3b3

@ -4,8 +4,19 @@ import (
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 {
// +kubebuilder:validation:Required
ServiceTokenSecretReference KubeSecretReference `json:"serviceTokenSecretReference"`
// +kubebuilder:validation:Required
SecretsScope SecretScopeInWorkspace `json:"secretsScope"`
}
type ServiceAccountDetails struct {
@ -14,11 +25,12 @@ type ServiceAccountDetails struct {
EnvironmentName string `json:"environmentName"`
}
type Authentication struct {
// +kubebuilder:validation:Optional
ServiceAccount ServiceAccountDetails `json:"serviceAccount"`
// +kubebuilder:validation:Optional
ServiceToken ServiceTokenDetails `json:"serviceToken"`
type SecretScopeInWorkspace struct {
// +kubebuilder:validation:Required
SecretsPath string `json:"secretsPath"`
// +kubebuilder:validation:Required
EnvSlug string `json:"envSlug"`
}
type KubeSecretReference struct {

@ -157,6 +157,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 *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.
func (in *ServiceAccountDetails) DeepCopyInto(out *ServiceAccountDetails) {
*out = *in
@ -177,6 +192,7 @@ func (in *ServiceAccountDetails) DeepCopy() *ServiceAccountDetails {
func (in *ServiceTokenDetails) DeepCopyInto(out *ServiceTokenDetails) {
*out = *in
out.ServiceTokenSecretReference = in.ServiceTokenSecretReference
out.SecretsScope = in.SecretsScope
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceTokenDetails.

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

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

@ -219,7 +219,10 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account")
} 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 {
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
}

@ -3,6 +3,8 @@ package util
import (
"encoding/base64"
"fmt"
"path"
"regexp"
"strings"
"github.com/Infisical/infisical/k8-operator/packages/api"
@ -48,7 +50,7 @@ func GetServiceTokenDetails(infisicalToken string) (api.GetServiceTokenDetailsRe
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)
if len(serviceTokenParts) < 4 {
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{
WorkspaceId: serviceTokenDetails.Workspace,
Environment: serviceTokenDetails.Environment,
Environment: envSlug,
ETag: etag,
SecretPath: serviceTokenDetails.SecretPath,
SecretPath: secretPath,
})
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 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.
@ -252,3 +257,104 @@ func GetPlainTextSecrets(key []byte, encryptedSecretsResponse api.GetEncryptedSe
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
}

Loading…
Cancel
Save