mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
add secret reference support
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user