diff --git a/internal/names.go b/internal/names.go index 119226bc8..2fffb0ae5 100644 --- a/internal/names.go +++ b/internal/names.go @@ -45,6 +45,10 @@ const ( CredentialRegistryName = Namespace + "registry-creds" // CredentialRegistryPath is the name to the credential containing registry credentials CredentialRegistryPath = "/cnab/app/registry-creds.json" + // SecretsPath is the directory where secret files are mounted + SecretsPath = "/cnab/app/secrets" + // SecretsParameterPrefix is the prefix used by WriteOnly parameters + SecretsParameterPrefix = Namespace + "secret." // ParameterOrchestratorName is the name of the parameter containing the orchestrator ParameterOrchestratorName = Namespace + "orchestrator" diff --git a/internal/packager/cnab.go b/internal/packager/cnab.go index db5f50087..731ab975c 100644 --- a/internal/packager/cnab.go +++ b/internal/packager/cnab.go @@ -108,6 +108,20 @@ func ToCNAB(app *types.App, invocationImageName string) (*bundle.Bundle, error) Definition: name, } } + for name, secret := range app.Secrets() { + writeOnly := new(bool) + *writeOnly = true + definitions[internal.SecretsParameterPrefix+name] = &definition.Schema{ + Type: "string", + WriteOnly: writeOnly, + } + parameters[internal.SecretsParameterPrefix+name] = bundle.Parameter{ + Destination: &bundle.Location{ + Path: secret.NormalizeFilename(), + }, + Definition: internal.SecretsParameterPrefix + name, + } + } var maintainers []bundle.Maintainer for _, m := range app.Metadata().Maintainers { maintainers = append(maintainers, bundle.Maintainer{ diff --git a/types/secrets/secrets.go b/types/secrets/secrets.go new file mode 100644 index 000000000..41ef4479c --- /dev/null +++ b/types/secrets/secrets.go @@ -0,0 +1,27 @@ +package secrets + +import ( + "crypto/sha256" + "fmt" +) + +// Secrets represents a secret map +type Secrets map[string]Secret + +// Secret represents a secret +type Secret struct { + Path string + External bool + Name string +} + +// New creates a new empty secret map +func New() Secrets { + return make(map[string]Secret) +} + +// NormalizeFilename generates a filename for this secret to be mounted in the invocation image +func (s *Secret) NormalizeFilename() string { + digest := sha256.Sum256([]byte(s.Path)) + return fmt.Sprintf("/cnab/app/secret/%x", digest) +} diff --git a/types/types.go b/types/types.go index 4565ea23b..6ff47f614 100644 --- a/types/types.go +++ b/types/types.go @@ -9,9 +9,13 @@ import ( "path/filepath" "strings" + composeloader "github.com/docker/cli/cli/compose/loader" + composetypes "github.com/docker/cli/cli/compose/types" + "github.com/docker/app/internal" "github.com/docker/app/types/metadata" "github.com/docker/app/types/parameters" + "github.com/docker/app/types/secrets" ) // AppSourceKind represents what format the app was in when read @@ -41,6 +45,7 @@ type App struct { composesContent [][]byte parametersContent [][]byte parameters parameters.Parameters + secrets secrets.Secrets metadataContent []byte metadata metadata.AppMetadata attachments []Attachment @@ -93,6 +98,11 @@ func (a *App) Attachments() []Attachment { return a.attachments } +// Secrets returns a map of secrets +func (a *App) Secrets() secrets.Secrets { + return a.secrets +} + func (a *App) HasCRLF() bool { return a.hasCRLF } @@ -288,10 +298,36 @@ func composeLoader(f func() ([][]byte, error)) func(app *App) error { return err } app.composesContent = append(app.composesContent, composesContent...) + app.secrets = secrets.New() + for _, c := range app.composesContent { + parsedCompose, err := composeloader.ParseYAML(c) + if err != nil { + return err + } + cfg, err := composeloader.Load(composetypes.ConfigDetails{ + ConfigFiles: []composetypes.ConfigFile{ + {Filename: "docker-compose.yml", Config: parsedCompose}, + }, + }, withSkipInterpolation) + if err != nil { + return err + } + for name, secret := range cfg.Secrets { + app.secrets[name] = secrets.Secret{ + Name: secret.Name, + Path: secret.File, + External: secret.External.External, + } + } + } return nil } } +func withSkipInterpolation(opts *composeloader.Options) { + opts.SkipInterpolation = true +} + func readReaders(readers ...io.Reader) ([][]byte, error) { content := make([][]byte, len(readers)) var errs []string