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

Continuation of the docker secret storage feature #6697

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
123 changes: 123 additions & 0 deletions api/client/commands.go
Expand Up @@ -71,12 +71,14 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
}

func (cli *DockerCli) CmdBuild(args ...string) error {
var flGrantSecrets = opts.NewListOpts(nil)
cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH")
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds")
cmd.Var(&flGrantSecrets, []string{"-grant-secret"}, "Grant build access to named secret")
if err := cmd.Parse(args); err != nil {
return nil
}
Expand Down Expand Up @@ -208,6 +210,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
v.Set("forcerm", "1")
}

for _, s := range flGrantSecrets.GetAll() {
v.Set("grant_secret", s)
}

cli.LoadConfigFile()

headers := http.Header(make(map[string][]string))
Expand Down Expand Up @@ -2510,3 +2516,120 @@ func (cli *DockerCli) CmdExec(args ...string) error {

return nil
}

func (cli *DockerCli) CmdSecret(_ bool, args ...string) error {
help := fmt.Sprintf("Usage: docker secret COMMAND [arg...]\n\nMaintain secrets database.\n\nCommands:\n")
for _, command := range [][]string{
{"add", "Add a secret"},
{"list", "List known secrets"},
{"rm", "Remove secret"},
} {
help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
}
fmt.Fprintf(cli.err, "%s\n", help)
return nil
}

func (cli *DockerCli) CmdSecretList(args ...string) error {
cmd := cli.Subcmd("secret list", "[OPTIONS]", "List file secrets available")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show names")
all := cmd.Bool([]string{"a", "-all"}, false, "Show all secrets (not only toplevel)")

if err := cmd.Parse(args); err != nil {
return nil
}

if cmd.NArg() > 0 {
cmd.Usage()
return nil
}

v := url.Values{}
if *all {
v.Set("all", "1")
}
body, _, err := readBody(cli.call("GET", "/secrets/json?"+v.Encode(), nil, false))
if err != nil {
return err
}

outs := engine.NewTable("Name", 0)
if _, err := outs.ReadListFrom(body); err != nil {
return err
}

w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprint(w, "SECRET NAME\n")
}

for _, out := range outs.Data {
name := out.Get("Name")
if out.GetBool("IsDir") {
name = name + "/"
}
fmt.Fprintf(w, "%s\n", name)
}
w.Flush()
return nil
}

func (cli *DockerCli) CmdSecretRm(args ...string) error {
cmd := cli.Subcmd("secret rm", "[OPTIONS] SECRETNAME [SECRETNAME...]", "Remove file secret from grants available")

if err := cmd.Parse(args); err != nil {
return nil
}

if cmd.NArg() == 0 {
cmd.Usage()
return nil
}

v := url.Values{}

var encounteredError error
for _, name := range cmd.Args() {
_, _, err := readBody(cli.call("DELETE", "/secrets/"+name+"?"+v.Encode(), nil, false))
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to remove one or more secrets")
} else {
fmt.Fprintf(cli.out, "%s\n", name)
}
}
return encounteredError
}

func (cli *DockerCli) CmdSecretAdd(args ...string) error {
cmd := cli.Subcmd("secret add", "[OPTIONS] DESTFILENAME SRCFILENAME", "Add file secret to grants available")

if err := cmd.Parse(args); err != nil {
return nil
}

if cmd.NArg() != 2 {
cmd.Usage()
return nil
}

name := cmd.Arg(0)
source := cmd.Arg(1)

var in io.Reader

if source == "-" {
in = cli.in
} else {
file, err := os.Open(source)
if err != nil {
return err
}
defer file.Close()
in = file
}

v := url.Values{}

return cli.stream("POST", "/secrets/"+name+"?"+v.Encode(), in, cli.out, nil)
}
53 changes: 53 additions & 0 deletions api/server/server.go
Expand Up @@ -280,6 +280,21 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW
return nil
}

func getSecretsJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
job := eng.Job("secrets_list")
job.Setenv("all", r.Form.Get("all"))

streamJSON(job, w, false)

if err := job.Run(); err != nil {
return err
}
return nil
}

func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if version.GreaterThan("1.6") {
w.WriteHeader(http.StatusNotFound)
Expand Down Expand Up @@ -553,6 +568,27 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon
return nil
}

func postSecrets(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}

var (
env engine.Env
name = vars["name"]
)
job := eng.Job("secret_add", name)
job.Stdin.Add(r.Body)

streamJSON(job, w, true)
if err := job.Run(); err != nil {
return err
}

env.Set("Name", name)
return writeJSON(w, http.StatusCreated, env)
}

func getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
Expand Down Expand Up @@ -751,6 +787,19 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr
return job.Run()
}

func deleteSecrets(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
job := eng.Job("secret_delete", vars["name"])
streamJSON(job, w, false)

return job.Run()
}

func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
Expand Down Expand Up @@ -1016,6 +1065,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
job.Setenv("q", r.FormValue("q"))
job.Setenv("nocache", r.FormValue("nocache"))
job.Setenv("forcerm", r.FormValue("forcerm"))
job.SetenvList("grant_secret", r.Form["grant_secret"])
job.SetenvJson("authConfig", authConfig)
job.SetenvJson("configFile", configFile)

Expand Down Expand Up @@ -1271,6 +1321,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
"/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/logs": getContainersLogs,
"/containers/{name:.*}/attach/ws": wsContainersAttach,
"/secrets/json": getSecretsJSON,
},
"POST": {
"/auth": postAuth,
Expand All @@ -1294,10 +1345,12 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
"/containers/{name:.*}/exec": postContainerExecCreate,
"/exec/{name:.*}/start": postContainerExecStart,
"/exec/{name:.*}/resize": postContainerExecResize,
"/secrets/{name:.*}": postSecrets,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
"/images/{name:.*}": deleteImages,
"/secrets/{name:.*}": deleteSecrets,
},
"OPTIONS": {
"": optionsHandler,
Expand Down
2 changes: 2 additions & 0 deletions builder/evaluator.go
Expand Up @@ -93,6 +93,8 @@ type Builder struct {
// both of these are controlled by the Remove and ForceRemove options in BuildOpts
TmpContainers map[string]struct{} // a map of containers used for removes

GrantSecrets []string

dockerfile *parser.Node // the syntax tree of the dockerfile
image string // image name for commit processing
maintainer string // maintainer name. could probably be removed.
Expand Down
2 changes: 2 additions & 0 deletions builder/internals.go
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/tarsum"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)

Expand Down Expand Up @@ -500,6 +501,7 @@ func (b *Builder) create() (*daemon.Container, error) {
if err != nil {
return nil, err
}
c.SetHostConfig(&runconfig.HostConfig{GrantSecrets: b.GrantSecrets})
for _, warning := range warnings {
fmt.Fprintf(b.OutStream, " ---> [Warning] %s\n", warning)
}
Expand Down
2 changes: 2 additions & 0 deletions builder/job.go
Expand Up @@ -36,6 +36,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
noCache = job.GetenvBool("nocache")
rm = job.GetenvBool("rm")
forceRm = job.GetenvBool("forcerm")
grantSecrets = job.GetenvList("grant_secret")
authConfig = &registry.AuthConfig{}
configFile = &registry.ConfigFile{}
tag string
Expand Down Expand Up @@ -116,6 +117,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
StreamFormatter: sf,
AuthConfig: authConfig,
AuthConfigFile: configFile,
GrantSecrets: grantSecrets,
}

id, err := builder.Run(context)
Expand Down
59 changes: 58 additions & 1 deletion daemon/container.go
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/docker/docker/pkg/broadcastwriter"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/log"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/networkfs/etchosts"
"github.com/docker/docker/pkg/networkfs/resolvconf"
"github.com/docker/docker/pkg/promise"
Expand Down Expand Up @@ -327,11 +328,27 @@ func (container *Container) Start() (err error) {
if err := populateCommand(container, env); err != nil {
return err
}
if err := container.setupSecretFiles(); err != nil {
return err
}
if err := container.setupMounts(); err != nil {
return err
}

return container.waitForStart()
if err := container.waitForStart(); err != nil {
return err
}

// Now the container is running, unmount the secrets on the host
secretsPath, err := container.secretsPath()
if err != nil {
return err
}
if err := mount.Unmount(secretsPath); err != nil {
return err
}

return nil
}

func (container *Container) Run() error {
Expand Down Expand Up @@ -571,6 +588,14 @@ func (container *Container) cleanup() {
}
}

if secretsPath, err := container.secretsPath(); err == nil {
// Ignore errors here as it may not be mounted anymore
mount.Unmount(secretsPath)
} else {
// but log for good measure
log.Errorf("%s: Error getting secretsPath: %s", container.ID, err)
}

if err := container.Unmount(); err != nil {
log.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
}
Expand Down Expand Up @@ -775,6 +800,10 @@ func (container *Container) jsonPath() (string, error) {
return container.getRootResourcePath("config.json")
}

func (container *Container) secretsPath() (string, error) {
return container.getRootResourcePath("secrets")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sugest you place the secrets in container.getRootResourcePath("secrets/secret-files") this way it will be easy to add a container.getRootResourcePath("secrets/secret-properties.json") file in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thought. That would make it easier for a next step of handling ACLs of
who added the secret and what is the permissions on it.
On Oct 9, 2014 4:06 AM, "Timothy Hobbs" notifications@github.com wrote:

In daemon/container.go:

@@ -775,6 +800,10 @@ func (container *Container) jsonPath() (string, error) {
return container.getRootResourcePath("config.json")
}

+func (container *Container) secretsPath() (string, error) {

  • return container.getRootResourcePath("secrets")

I sugest you place the secrets in
container.getRootResourcePath("secrets/secret-files") this way it will be
easy to add a
container.getRootResourcePath("secrets/secret-properties.json") file in
the future.


Reply to this email directly or view it on GitHub
https://github.com/docker/docker/pull/6697/files#r18629350.

}

// This method must be exported to be used from the lxc template
// This directory is only usable when the container is running
func (container *Container) RootfsPath() string {
Expand Down Expand Up @@ -1034,6 +1063,34 @@ func (container *Container) verifyDaemonSettings() {
}
}

func (container *Container) setupSecretFiles() error {
secretsPath, err := container.secretsPath()
if err != nil {
return fmt.Errorf("failed to get secretsPath: %s", err)
}

if err := os.MkdirAll(secretsPath, 0700); err != nil {
return err
}

if err := mount.Mount("tmpfs", secretsPath, "tmpfs", "nosuid,nodev,noexec"); err != nil {
return fmt.Errorf("mounting secret tmpfs: %s", err)
}

for _, granted := range container.hostConfig.GrantSecrets {
data, err := container.daemon.secrets.GetData(granted)
if err != nil {
return err
}

for _, s := range data {
s.SaveTo(secretsPath)
}
}

return nil
}

func (container *Container) setupLinkedContainers() ([]string, error) {
var (
env []string
Expand Down