Skip to content

Commit

Permalink
leverage file temaplates with k8s-secret targets
Browse files Browse the repository at this point in the history
  • Loading branch information
Fürst Roman committed Feb 11, 2025
1 parent d563d22 commit 2bd80fd
Show file tree
Hide file tree
Showing 20 changed files with 615 additions and 204 deletions.
1 change: 1 addition & 0 deletions pkg/log/messages/debug_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ const CSPFK007D string = "CSPFK007D Kubernetes Secret '%s' has an invalid value
const CSPFK008D string = "CSPFK008D Kubernetes Secret '%s' has no '%s' data entry defined"
const CSPFK009D string = "CSPFK009D Processing '%s' data entry value of Kubernetes Secret '%s'"
const CSPFK010D string = "CSPFK010D Listed %d secrets from Conjur"
const CSPFK011D string = "CSPFK011D Kubernetes Secret '%s' has no '%s' annotation defined"
4 changes: 4 additions & 0 deletions pkg/secrets/clients/k8s/k8s_secrets_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ func UpdateK8sSecret(namespace string, secretName string, originalK8sSecret *v1.
// get K8s client object
kubeClient, _ := configK8sClient()

if originalK8sSecret.Data == nil {
originalK8sSecret.Data = map[string][]byte{}
}

for secretName, secretValue := range stringDataEntriesMap {
originalK8sSecret.Data[secretName] = secretValue
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/secrets/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ func NewConfig(settings map[string]string) *Config {
k8sSecretsStr = strings.ReplaceAll(k8sSecretsStr, " ", "")
k8sSecretsArr = strings.Split(k8sSecretsStr, ",")
}
k8sSecretsArr = removeEmptyStrings(k8sSecretsArr)
}

retryCountLimitStr := settings[retryCountLimitKey]
Expand Down Expand Up @@ -418,3 +419,13 @@ func validRefreshInterval(intervalStr string, enableStr string, envAndAnnots map
}
return err
}

func removeEmptyStrings(s []string) []string {
var r []string
for _, str := range s {
if str != "" {
r = append(r, str)
}
}
return r
}
48 changes: 48 additions & 0 deletions pkg/secrets/file_templates/secret_file_templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package filetemplates

import (
"bytes"
"fmt"
"text/template"
)

const SecretGroupPrefix = "conjur.org/conjur-secrets."
const SecretGroupFileTemplatePrefix = "conjur.org/secret-file-template."

// Secret describes how Conjur secrets are represented in the file-template-rendering context.
type Secret struct {
Alias string
Value string
}

// templateData describes the form in which data is presented to file templates
type TemplateData struct {
SecretsArray []*Secret
SecretsMap map[string]*Secret
}

func RenderFile(tpl *template.Template, tplData TemplateData) (*bytes.Buffer, error) {
buf := &bytes.Buffer{}
err := tpl.Execute(buf, tplData)
return buf, err
}

func GetTemplate(name string, secretsMap map[string]*Secret) *template.Template {

return template.New(name).Funcs(template.FuncMap{
// secret is a custom utility function for streamlined access to secret values.
// It panics for secrets aliases not specified on the group.
"secret": func(alias string) string {
v, ok := secretsMap[alias]
if ok {
return v.Value
}

// Panic in a template function is captured as an error
// when the template is executed.
panic(fmt.Sprintf("secret alias %q not present in specified secrets for group", alias))
},
"b64enc": b64encTemplateFunc,
"b64dec": b64decTemplateFunc,
})
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pushtofile
package filetemplates

import (
"fmt"
Expand All @@ -10,7 +10,7 @@ import (
)

const (
maxConjurVarNameLen = 126
MaxConjurVarNameLen = 126
)

// SecretSpec specifies a secret to be retrieved from Conjur by defining
Expand All @@ -22,6 +22,14 @@ type SecretSpec struct {
ContentType string
}

// SecretGroup incorporates common information about a secret group
// that has been parsed from that secret group's Annotations.
type SecretGroup struct {
Name string
FileTemplate string
SecretSpecs []SecretSpec
}

// MarshalYAML is a custom marshaller for SecretSpec.
func (t SecretSpec) MarshalYAML() (interface{}, error) {
return map[string]string{t.Alias: t.Path, "ContentType": t.ContentType}, nil
Expand Down Expand Up @@ -112,11 +120,11 @@ func NewSecretSpecs(raw []byte) ([]SecretSpec, error) {
return secretSpecs, nil
}

func validateSecretPathsAndContents(secretSpecs []SecretSpec, groupName string) []error {
func ValidateSecretPathsAndContents(secretSpecs []SecretSpec, groupName string) []error {

errors := validateSecretPaths(secretSpecs, groupName)
errors := ValidateSecretPaths(secretSpecs, groupName)

errs := validateSecretContents(secretSpecs, groupName)
errs := ValidateSecretContents(secretSpecs, groupName)
if errs != nil {
for _, err := range errs {
// Log the errors as warnings but allow it to proceed
Expand All @@ -126,7 +134,7 @@ func validateSecretPathsAndContents(secretSpecs []SecretSpec, groupName string)
return errors
}

func validateSecretPaths(secretSpecs []SecretSpec, groupName string) []error {
func ValidateSecretPaths(secretSpecs []SecretSpec, groupName string) []error {
var errors []error
for _, secretSpec := range secretSpecs {
if err := validateSecretPath(secretSpec.Path, groupName); err != nil {
Expand All @@ -136,7 +144,7 @@ func validateSecretPaths(secretSpecs []SecretSpec, groupName string) []error {
return errors
}

func validateSecretContents(secretSpecs []SecretSpec, groupName string) []error {
func ValidateSecretContents(secretSpecs []SecretSpec, groupName string) []error {
var errors []error
for _, secretSpec := range secretSpecs {
if err := validateSecretContent(secretSpec.ContentType, groupName); err != nil {
Expand Down Expand Up @@ -169,9 +177,9 @@ func validateSecretPath(path, groupName string) error {

// The Conjur variable name (the last word in the Conjur variable path)
// must be no longer than 126 characters.
if len(varName) > maxConjurVarNameLen {
if len(varName) > MaxConjurVarNameLen {
return fmt.Errorf("Secret group %s: the Conjur variable name '%s' is longer than %d characters",
groupName, varName, maxConjurVarNameLen)
groupName, varName, MaxConjurVarNameLen)
}

return nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pushtofile
package filetemplates

import "encoding/base64"

Expand Down
28 changes: 20 additions & 8 deletions pkg/secrets/k8s_secrets_storage/mocks/k8s_secrets_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package mocks

import (
"errors"

"gopkg.in/yaml.v3"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// K8sSecrets represents a collection of Kubernetes Secrets to be populated
Expand All @@ -31,15 +31,20 @@ type k8sSecretDataValues map[string]interface{}
// (respectively) to a custom error.
type KubeSecretsClient struct {
// Mocks a K8s database. Maps k8s secret names to K8s secrets.
database map[string]map[string][]byte
database map[string]K8sSecretsContent
ErrOnRetrieve error
ErrOnUpdate error
}

type K8sSecretsContent struct {
annotations map[string]string
data map[string][]byte
}

// NewKubeSecretsClient creates an instance of a KubeSecretsClient
func NewKubeSecretsClient() *KubeSecretsClient {
return &KubeSecretsClient{
database: map[string]map[string][]byte{},
database: map[string]K8sSecretsContent{},
ErrOnRetrieve: nil,
ErrOnUpdate: nil,
}
Expand All @@ -49,6 +54,7 @@ func NewKubeSecretsClient() *KubeSecretsClient {
// database.
func (c *KubeSecretsClient) AddSecret(
secretName string,
annotations map[string]string,
secretData k8sSecretData,
) {
// Convert string values to YAML format
Expand All @@ -61,7 +67,10 @@ func (c *KubeSecretsClient) AddSecret(
yamlizedSecretData[key] = yamlValue
}

c.database[secretName] = yamlizedSecretData
c.database[secretName] = K8sSecretsContent{
annotations: annotations,
data: yamlizedSecretData,
}
}

// RetrieveSecret retrieves a Kubernetes Secret from the mock Kubernetes
Expand All @@ -73,13 +82,16 @@ func (c *KubeSecretsClient) RetrieveSecret(_ string, secretName string) (*v1.Sec
}

// Check if the secret exists in the mock K8s DB
secretData, ok := c.database[secretName]
secretContent, ok := c.database[secretName]
if !ok {
return nil, errors.New("custom error")
}

return &v1.Secret{
Data: secretData,
ObjectMeta: metav1.ObjectMeta{
Annotations: secretContent.annotations,
},
Data: secretContent.data,
}, nil
}

Expand All @@ -96,7 +108,7 @@ func (c *KubeSecretsClient) UpdateSecret(

secretToUpdate := c.database[secretName]
for key, value := range stringDataEntriesMap {
secretToUpdate[key] = value
secretToUpdate.data[key] = value
}

return nil
Expand All @@ -106,5 +118,5 @@ func (c *KubeSecretsClient) UpdateSecret(
// content of a Kubernetes Secret by reading this content directly from
// the mock client's database.
func (c *KubeSecretsClient) InspectSecret(secretName string) map[string][]byte {
return c.database[secretName]
return c.database[secretName].data
}
Loading

0 comments on commit 2bd80fd

Please sign in to comment.