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"
|
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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user