Skip to content

Commit

Permalink
Fix: create missing directories when pushing to file
Browse files Browse the repository at this point in the history
  • Loading branch information
doodlesbykumbi committed Oct 29, 2021
1 parent 24f2068 commit c6071f4
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 92 deletions.
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,
})
}

155 changes: 104 additions & 51 deletions pkg/secrets/pushtofile/secret_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ func NewSecretGroups(secretsBasePath string, annotations map[string]string) ([]*

func newSecretGroup(groupName string, secretsBasePath string, annotations map[string]string) (*SecretGroup, []error) {
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 @@ -195,81 +205,124 @@ func newSecretGroup(groupName string, secretsBasePath string, annotations map[st
err,
)
return nil, []error{err}

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

errors := validateGroup(
groupName,
fileFormat,
secretSpecs,
)
if len(errors) > 0 {
return nil, errors
}

fileTemplate := annotations[secretGroupFileTemplatePrefix+groupName]
filePath := annotations[secretGroupFilePathPrefix+groupName]
fileFormat := annotations[secretGroupFileFormatPrefix+groupName]
policyPathPrefix := annotations[secretGroupPolicyPathPrefix+groupName]
// Validate and generate relative file path
filePath, err = filePathForGroup(
groupName,
filePath,
fileTemplate,
fileFormat,
)
if err != nil {
return nil, []error{err}
}

// Default to "yaml" file format
if len(fileTemplate)+len(fileFormat) == 0 {
fileFormat = "yaml"
// Generate absolute file path for group
filePath, err = absoluteFilePathForGroup(
groupName,
secretsBasePath,
filePath,
)
if err != nil {
return nil, []error{err}
}

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

func validateGroup(
groupName string,
fileFormat string,
secretSpecs []SecretSpec,
) []error {
if errors := validateSecretPaths(secretSpecs, groupName); len(errors) > 0 {
return errors
}

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 []error{
fmt.Errorf(
"unable to process file format annotation %q for group: %s",
fileFormat,
err,
),
}
}
}

return nil
}

func filePathForGroup(
groupName string,
filePath string,
fileTemplate string,
fileFormat string,
) (string, error) {
// filePath must be relative
if path.IsAbs(filePath) {
return nil, []error{
fmt.Errorf(
"provided filepath %q for secret group %q is absolute, requires relative path",
filePath, groupName,
),
}
}

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 nil, []error{
fmt.Errorf(
"provided filepath %q for secret group %q must be relative to secrets base path",
filePath, groupName,
),
}
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 nil, []error{
fmt.Errorf(
"provided filepath %q for secret group %q must specify a path to a file (without a trailing %q), required when %q is configured",
filePath, groupName, "/", secretGroupFileTemplatePrefix + "{groupName}",
),
}
return "", fmt.Errorf(
"provided filepath %q for secret group %q must specify a path to a file (without a trailing %q), required when %q is configured",
filePath, groupName, "/", secretGroupFileTemplatePrefix+"{groupName}",
)
}
// Without the restrictions of fileTemplate, the filename defaults to "{groupName}.{fileFormat}"
if filePathIsDir && len(fileTemplate) == 0 {
absoluteFilePath = path.Join(absoluteFilePath, fmt.Sprintf("%s.%s", groupName, fileFormat))
filePath = path.Join(
filePath,
fmt.Sprintf("%s.%s", groupName, fileFormat),
)
}

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

func absoluteFilePathForGroup(
groupName string,
secretsBasePath string,
filePath string,
) (string, error) {
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
}
Loading

0 comments on commit c6071f4

Please sign in to comment.