Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorporate secrets base path into secret group filepath #381

Merged
merged 5 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/secrets-provider/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
const (
defaultContainerMode = "init"
annotationsFilePath = "/conjur/podinfo/annotations"
secretBasePath = "/conjur/secrets"
secretsBasePath = "/conjur/secrets"
)

var annotationsMap map[string]string
Expand Down Expand Up @@ -69,6 +69,7 @@ func main() {
secretsConfig.StoreType,
secretsConfig.PodNamespace,
secretsConfig.RequiredK8sSecrets,
secretsBasePath,
annotationsMap,
)
logErrorsAndConditionalExit(errs, nil, messages.CSPFK053E)
Expand Down
2 changes: 2 additions & 0 deletions pkg/secrets/provide_conjur_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewProviderForType(
storeType string,
podNamespace string,
requiredK8sSecrets []string,
secretsBasePath string,
annotations map[string]string,
) (ProviderFunc, []error) {
switch storeType {
Expand All @@ -42,6 +43,7 @@ func NewProviderForType(
case config.File:
provider, err := pushtofile.NewProvider(
secretsRetrieverFunc,
secretsBasePath,
annotations,
)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/secrets/pushtofile/provide_conjur_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ type fileProvider struct {
}

// NewProvider creates a new provider for Push-to-File mode.
func NewProvider(retrieveSecretsFunc conjur.RetrieveSecretsFunc, annotations map[string]string) (*fileProvider, []error) {
secretGroups, err := NewSecretGroups(annotations)
func NewProvider(retrieveSecretsFunc conjur.RetrieveSecretsFunc, secretsBasePath string, annotations map[string]string) (*fileProvider, []error) {
secretGroups, err := NewSecretGroups(secretsBasePath, annotations)
if err != nil {
return nil, err
}
Expand Down
8 changes: 5 additions & 3 deletions pkg/secrets/pushtofile/provide_conjur_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ func TestNewProvider(t *testing.T) {
TestCases := []struct {
description string
retrieveFunc conjur.RetrieveSecretsFunc
basePath string
annotations map[string]string
expectedSecretGroup []*SecretGroup
}{
{
description: "happy case",
retrieveFunc: retrieve,
basePath: "/basepath",
annotations: map[string]string{
"conjur.org/conjur-secrets.groupname": "- password: path1\n",
"conjur.org/secret-file-path.groupname": "path/to/file",
Expand All @@ -34,7 +36,7 @@ func TestNewProvider(t *testing.T) {
expectedSecretGroup: []*SecretGroup{
{
Name: "groupname",
FilePath: "path/to/file",
FilePath: "/basepath/path/to/file",
FileTemplate: "",
FileFormat: "yaml",
FilePermissions: defaultFilePermissions,
Expand All @@ -51,7 +53,7 @@ func TestNewProvider(t *testing.T) {

for _, tc := range TestCases {
t.Run(tc.description, func(t *testing.T) {
p, err := NewProvider(tc.retrieveFunc, tc.annotations)
p, err := NewProvider(tc.retrieveFunc, tc.basePath, tc.annotations)
assert.Empty(t, err)
assert.Equal(t, tc.expectedSecretGroup, p.secretGroups)
})
Expand All @@ -71,7 +73,7 @@ func TestProvideWithDeps(t *testing.T) {
secretGroups: []*SecretGroup{
{
Name: "groupname",
FilePath: "path/to/file",
FilePath: "/path/to/file",
FileFormat: "yaml",
FilePermissions: 123,
SecretSpecs: []SecretSpec{
Expand Down
16 changes: 14 additions & 2 deletions pkg/secrets/pushtofile/push_to_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"text/template"
)

Expand Down Expand Up @@ -31,7 +32,19 @@ type openWriteCloserFunc func(

// openFileAsWriteCloser opens a file to write-to with some permissions.
func openFileAsWriteCloser(path string, permissions os.FileMode) (io.WriteCloser, error) {
return os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, permissions)
dir := filepath.Dir(path)

err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return nil, fmt.Errorf("unable to mkdir when opening file to write at %q: %s", path, err)
}

wc, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, permissions)
if err != nil {
return nil, fmt.Errorf("unable to open file to write at %q: %s", path, err)
}

return wc, nil
}

// pushToWriter takes a (group's) path, template and secrets, and processes the template
Expand Down Expand Up @@ -70,4 +83,3 @@ func pushToWriter(
SecretsMap: secretsMap,
})
}

158 changes: 116 additions & 42 deletions pkg/secrets/pushtofile/secret_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pushtofile
import (
"fmt"
"os"
"path"
"sort"
"strings"
)
Expand All @@ -29,17 +30,17 @@ type SecretGroup struct {

// ResolvedSecretSpecs resolves all of the secret paths for a secret
// group by prepending each path with that group's policy path prefix.
func (s *SecretGroup) ResolvedSecretSpecs() []SecretSpec {
if len(s.PolicyPathPrefix) == 0 {
return s.SecretSpecs
func (sg *SecretGroup) ResolvedSecretSpecs() []SecretSpec {
if len(sg.PolicyPathPrefix) == 0 {
return sg.SecretSpecs
}

specs := make([]SecretSpec, len(s.SecretSpecs))
copy(specs, s.SecretSpecs)
specs := make([]SecretSpec, len(sg.SecretSpecs))
copy(specs, sg.SecretSpecs)

// Update specs with policy path prefix
for i := range specs {
specs[i].Path = strings.TrimSuffix(s.PolicyPathPrefix, "/") +
specs[i].Path = strings.TrimSuffix(sg.PolicyPathPrefix, "/") +
"/" +
strings.TrimPrefix(specs[i].Path, "/")
}
Expand All @@ -49,16 +50,16 @@ func (s *SecretGroup) ResolvedSecretSpecs() []SecretSpec {

// PushToFile uses the configuration on a secret group to inject secrets into a template
// and write the result to a file.
func (s *SecretGroup) PushToFile(secrets []*Secret) error {
return s.pushToFileWithDeps(openFileAsWriteCloser, pushToWriter, secrets)
func (sg *SecretGroup) PushToFile(secrets []*Secret) error {
return sg.pushToFileWithDeps(openFileAsWriteCloser, pushToWriter, secrets)
}

func (s *SecretGroup) pushToFileWithDeps(
func (sg *SecretGroup) pushToFileWithDeps(
depOpenWriteCloser openWriteCloserFunc,
depPushToWriter pushToWriterFunc,
secrets []*Secret) error {
// Make sure all the secret specs are accounted for
err := validateSecretsAgainstSpecs(secrets, s.SecretSpecs)
err := validateSecretsAgainstSpecs(secrets, sg.SecretSpecs)
if err != nil {
return err
}
Expand All @@ -68,16 +69,16 @@ func (s *SecretGroup) pushToFileWithDeps(
// 2. File format
// 3. Secret specs (user to validate file template)
fileTemplate, err := maybeFileTemplateFromFormat(
s.FileTemplate,
s.FileFormat,
s.SecretSpecs,
sg.FileTemplate,
sg.FileFormat,
sg.SecretSpecs,
)
if err != nil {
return err
}

//// Open and push to file
wc, err := depOpenWriteCloser(s.FilePath, s.FilePermissions)
wc, err := depOpenWriteCloser(sg.FilePath, sg.FilePermissions)
if err != nil {
return err
}
Expand All @@ -87,12 +88,83 @@ func (s *SecretGroup) pushToFileWithDeps(

return depPushToWriter(
wc,
s.Name,
sg.Name,
fileTemplate,
secrets,
)
}

func (sg *SecretGroup) absoluteFilePath(secretsBasePath string) (string, error) {
groupName := sg.Name
filePath := sg.FilePath
fileTemplate := sg.FileTemplate
fileFormat := sg.FileFormat

// filePath must be relative
if path.IsAbs(filePath) {
return "", fmt.Errorf(
"provided filepath %q for secret group %q is absolute, requires relative path",
filePath, groupName,
)
}

filePathIsDir := strings.HasSuffix(filePath, "/")

// fileTemplate requires filePath to point to a file (not a directory)
if filePathIsDir && len(fileTemplate) > 0 {
return "", fmt.Errorf(
"provided filepath %q for secret group %q must specify a path to a file, without a trailing %q",
filePath, groupName, "/",
)
}
// Without the restrictions of fileTemplate, the filename defaults to "{groupName}.{fileFormat}"
if filePathIsDir && len(fileTemplate) == 0 {
filePath = path.Join(
filePath,
fmt.Sprintf("%s.%s", groupName, fileFormat),
)
}

absoluteFilePath := path.Join(secretsBasePath, filePath)

// filePath must be relative to secrets base path. This protects against relative paths
// that, by using the double-dot path segment, resolve to a path that is not relative
// to the base path.
if !strings.HasPrefix(absoluteFilePath, secretsBasePath) {
return "", fmt.Errorf(
"provided filepath %q for secret group %q must be relative to secrets base path",
filePath, groupName,
)
}

return absoluteFilePath, nil
}

func (sg *SecretGroup) validate() []error {
groupName := sg.Name
fileFormat := sg.FileFormat
secretSpecs := sg.SecretSpecs

if errors := validateSecretPaths(secretSpecs, groupName); len(errors) > 0 {
return errors
}

if len(fileFormat) > 0 {
_, err := FileTemplateForFormat(fileFormat, secretSpecs)
if err != nil {
return []error{
fmt.Errorf(
"unable to process file format %q for group: %s",
fileFormat,
err,
),
}
}
}

return nil
}

func validateSecretsAgainstSpecs(
secrets []*Secret,
specs []SecretSpec,
Expand Down Expand Up @@ -133,7 +205,7 @@ func maybeFileTemplateFromFormat(
fileFormat string,
secretSpecs []SecretSpec,
) (string, error) {
// One of file format or file template must be set
// Default to "yaml" file format
if len(fileTemplate)+len(fileFormat) == 0 {
fileFormat = "yaml"
}
Expand All @@ -156,14 +228,14 @@ func maybeFileTemplateFromFormat(
}

// NewSecretGroups creates a collection of secret groups from a map of annotations
func NewSecretGroups(annotations map[string]string) ([]*SecretGroup, []error) {
func NewSecretGroups(secretsBasePath string, annotations map[string]string) ([]*SecretGroup, []error) {
var sgs []*SecretGroup

var errors []error
for key := range annotations {
if strings.HasPrefix(key, secretGroupPrefix) {
groupName := strings.TrimPrefix(key, secretGroupPrefix)
sg, errs := newSecretGroup(annotations, groupName)
sg, errs := newSecretGroup(groupName, secretsBasePath, annotations)
if errs != nil {
errors = append(errors, errs...)
continue
Expand All @@ -184,8 +256,18 @@ func NewSecretGroups(annotations map[string]string) ([]*SecretGroup, []error) {
return sgs, nil
}

func newSecretGroup(annotations map[string]string, groupName string) (*SecretGroup, []error) {
func newSecretGroup(groupName string, secretsBasePath string, annotations map[string]string) (*SecretGroup, []error) {
doodlesbykumbi marked this conversation as resolved.
Show resolved Hide resolved
doodlesbykumbi marked this conversation as resolved.
Show resolved Hide resolved
groupSecrets := annotations[secretGroupPrefix+groupName]
fileTemplate := annotations[secretGroupFileTemplatePrefix+groupName]
filePath := annotations[secretGroupFilePathPrefix+groupName]
fileFormat := annotations[secretGroupFileFormatPrefix+groupName]
policyPathPrefix := annotations[secretGroupPolicyPathPrefix+groupName]

// Default to "yaml" file format
if len(fileTemplate)+len(fileFormat) == 0 {
fileFormat = "yaml"
}

secretSpecs, err := NewSecretSpecs([]byte(groupSecrets))
if err != nil {
err = fmt.Errorf(
Expand All @@ -194,36 +276,28 @@ func newSecretGroup(annotations map[string]string, groupName string) (*SecretGro
err,
)
return nil, []error{err}

}
if errors := validateSecretPaths(secretSpecs, groupName); err != nil {
return nil, errors
}

fileTemplate := annotations[secretGroupFileTemplatePrefix+groupName]
filePath := annotations[secretGroupFilePathPrefix+groupName]
fileFormat := annotations[secretGroupFileFormatPrefix+groupName]
policyPathPrefix := annotations[secretGroupPolicyPathPrefix+groupName]

if len(fileFormat) > 0 {
_, err := FileTemplateForFormat(fileFormat, secretSpecs)
if err != nil {
err = fmt.Errorf(
`unable to process file format annotation %q for group: %s`,
fileFormat,
err,
)
return nil, []error{err}
}
}

return &SecretGroup{
sg := &SecretGroup{
Name: groupName,
FilePath: filePath,
FileTemplate: fileTemplate,
FileFormat: fileFormat,
FilePermissions: defaultFilePermissions,
PolicyPathPrefix: policyPathPrefix,
SecretSpecs: secretSpecs,
}, nil
}

errors := sg.validate()
if len(errors) > 0 {
return nil, errors
}

// Generate absolute file path
sg.FilePath, err = sg.absoluteFilePath(secretsBasePath)
if err != nil {
return nil, []error{err}
}

return sg, nil
}
Loading