mirror of https://github.com/docker/cli.git
secrets: secret management for swarm
Signed-off-by: Evan Hazlett <ejhazlett@gmail.com> wip: use tmpfs for swarm secrets Signed-off-by: Evan Hazlett <ejhazlett@gmail.com> wip: inject secrets from swarm secret store Signed-off-by: Evan Hazlett <ejhazlett@gmail.com> secrets: use secret names in cli for service create Signed-off-by: Evan Hazlett <ejhazlett@gmail.com> switch to use mounts instead of volumes Signed-off-by: Evan Hazlett <ejhazlett@gmail.com> vendor: use ehazlett swarmkit Signed-off-by: Evan Hazlett <ejhazlett@gmail.com> secrets: finish secret update Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
This commit is contained in:
parent
a11f7b1577
commit
1be644fbcf
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/docker/docker/cli/command/node"
|
||||
"github.com/docker/docker/cli/command/plugin"
|
||||
"github.com/docker/docker/cli/command/registry"
|
||||
"github.com/docker/docker/cli/command/secret"
|
||||
"github.com/docker/docker/cli/command/service"
|
||||
"github.com/docker/docker/cli/command/stack"
|
||||
"github.com/docker/docker/cli/command/swarm"
|
||||
|
@ -25,6 +26,7 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
|
|||
node.NewNodeCommand(dockerCli),
|
||||
service.NewServiceCommand(dockerCli),
|
||||
swarm.NewSwarmCommand(dockerCli),
|
||||
secret.NewSecretCommand(dockerCli),
|
||||
container.NewContainerCommand(dockerCli),
|
||||
image.NewImageCommand(dockerCli),
|
||||
system.NewSystemCommand(dockerCli),
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
)
|
||||
|
||||
// NewSecretCommand returns a cobra command for `secret` subcommands
|
||||
func NewSecretCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "secret",
|
||||
Short: "Manage Docker secrets",
|
||||
Args: cli.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newSecretListCommand(dockerCli),
|
||||
newSecretCreateCommand(dockerCli),
|
||||
newSecretInspectCommand(dockerCli),
|
||||
newSecretRemoveCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "create [name]",
|
||||
Short: "Create a secret using stdin as content",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts := createOptions{
|
||||
name: args[0],
|
||||
}
|
||||
|
||||
return runSecretCreate(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runSecretCreate(dockerCli *command.DockerCli, opts createOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
secretData, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading content from STDIN: %v", err)
|
||||
}
|
||||
|
||||
spec := swarm.SecretSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: opts.name,
|
||||
},
|
||||
Data: secretData,
|
||||
}
|
||||
|
||||
r, err := client.SecretCreate(ctx, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(dockerCli.Out(), r.ID)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/cli/command/inspect"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type inspectOptions struct {
|
||||
name string
|
||||
format string
|
||||
}
|
||||
|
||||
func newSecretInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
opts := inspectOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [name]",
|
||||
Short: "Inspect a secret",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.name = args[0]
|
||||
return runSecretInspect(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runSecretInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
getRef := func(name string) (interface{}, []byte, error) {
|
||||
return client.SecretInspectWithRaw(ctx, name)
|
||||
}
|
||||
|
||||
return inspect.Inspect(dockerCli.Out(), []string{opts.name}, opts.format, getRef)
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
quiet bool
|
||||
}
|
||||
|
||||
func newSecretListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
opts := listOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ls",
|
||||
Short: "List secrets",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runSecretList(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runSecretList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
secrets, err := client.SecretList(ctx, types.SecretListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
|
||||
if opts.quiet {
|
||||
for _, s := range secrets {
|
||||
fmt.Fprintf(w, "%s\n", s.ID)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(w, "ID\tNAME\tCREATED\tUPDATED\tSIZE")
|
||||
fmt.Fprintf(w, "\n")
|
||||
|
||||
for _, s := range secrets {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", s.ID, s.Spec.Annotations.Name, s.Meta.CreatedAt, s.Meta.UpdatedAt, s.SecretSize)
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
ids []string
|
||||
}
|
||||
|
||||
func newSecretRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "rm [id]",
|
||||
Short: "Remove a secret",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts := removeOptions{
|
||||
ids: args,
|
||||
}
|
||||
return runSecretRemove(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runSecretRemove(dockerCli *command.DockerCli, opts removeOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
for _, id := range opts.ids {
|
||||
if err := client.SecretRemove(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(dockerCli.Out(), id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -58,6 +58,13 @@ func runCreate(dockerCli *command.DockerCli, opts *serviceOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// parse and validate secrets
|
||||
secrets, err := parseSecrets(apiClient, opts.secrets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.TaskTemplate.ContainerSpec.Secrets = secrets
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// only send auth if flag was set
|
||||
|
|
|
@ -191,6 +191,19 @@ func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
|
|||
return nets
|
||||
}
|
||||
|
||||
func convertSecrets(secrets []string) []*swarm.SecretReference {
|
||||
sec := []*swarm.SecretReference{}
|
||||
for _, s := range secrets {
|
||||
sec = append(sec, &swarm.SecretReference{
|
||||
SecretID: s,
|
||||
Mode: swarm.SecretReferenceFile,
|
||||
Target: "",
|
||||
})
|
||||
}
|
||||
|
||||
return sec
|
||||
}
|
||||
|
||||
type endpointOptions struct {
|
||||
mode string
|
||||
ports opts.ListOpts
|
||||
|
@ -337,6 +350,7 @@ type serviceOptions struct {
|
|||
logDriver logDriverOptions
|
||||
|
||||
healthcheck healthCheckOptions
|
||||
secrets []string
|
||||
}
|
||||
|
||||
func newServiceOptions() *serviceOptions {
|
||||
|
@ -403,6 +417,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
|||
Options: opts.dnsOptions.GetAll(),
|
||||
},
|
||||
StopGracePeriod: opts.stopGrace.Value(),
|
||||
Secrets: convertSecrets(opts.secrets),
|
||||
},
|
||||
Networks: convertNetworks(opts.networks.GetAll()),
|
||||
Resources: opts.resources.ToResourceRequirements(),
|
||||
|
@ -488,6 +503,7 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
|
|||
flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
|
||||
|
||||
flags.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY")
|
||||
flags.StringSliceVar(&opts.secrets, flagSecret, []string{}, "Specify secrets to expose to the service")
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -553,4 +569,5 @@ const (
|
|||
flagHealthRetries = "health-retries"
|
||||
flagHealthTimeout = "health-timeout"
|
||||
flagNoHealthcheck = "no-healthcheck"
|
||||
flagSecret = "secret"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
// parseSecretString parses the requested secret and returns the secret name
|
||||
// and target. Expects format SECRET_NAME:TARGET
|
||||
func parseSecretString(secretString string) (string, string, error) {
|
||||
tokens := strings.Split(secretString, ":")
|
||||
|
||||
secretName := strings.TrimSpace(tokens[0])
|
||||
targetName := ""
|
||||
|
||||
if secretName == "" {
|
||||
return "", "", fmt.Errorf("invalid secret name provided")
|
||||
}
|
||||
|
||||
if len(tokens) > 1 {
|
||||
targetName = strings.TrimSpace(tokens[1])
|
||||
if targetName == "" {
|
||||
return "", "", fmt.Errorf("invalid presentation name provided")
|
||||
}
|
||||
} else {
|
||||
targetName = secretName
|
||||
}
|
||||
return secretName, targetName, nil
|
||||
}
|
||||
|
||||
// parseSecrets retrieves the secrets from the requested names and converts
|
||||
// them to secret references to use with the spec
|
||||
func parseSecrets(client client.APIClient, requestedSecrets []string) ([]*swarmtypes.SecretReference, error) {
|
||||
lookupSecretNames := []string{}
|
||||
needSecrets := make(map[string]*swarmtypes.SecretReference)
|
||||
ctx := context.Background()
|
||||
|
||||
for _, secret := range requestedSecrets {
|
||||
n, t, err := parseSecretString(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secretRef := &swarmtypes.SecretReference{
|
||||
SecretName: n,
|
||||
Mode: swarmtypes.SecretReferenceFile,
|
||||
Target: t,
|
||||
}
|
||||
|
||||
lookupSecretNames = append(lookupSecretNames, n)
|
||||
needSecrets[n] = secretRef
|
||||
}
|
||||
|
||||
args := filters.NewArgs()
|
||||
for _, s := range lookupSecretNames {
|
||||
args.Add("names", s)
|
||||
}
|
||||
|
||||
secrets, err := client.SecretList(ctx, types.SecretListOptions{
|
||||
Filter: args,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
foundSecrets := make(map[string]*swarmtypes.Secret)
|
||||
for _, secret := range secrets {
|
||||
foundSecrets[secret.Spec.Annotations.Name] = &secret
|
||||
}
|
||||
|
||||
addedSecrets := []*swarmtypes.SecretReference{}
|
||||
|
||||
for secretName, secretRef := range needSecrets {
|
||||
s, ok := foundSecrets[secretName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret not found: %s", secretName)
|
||||
}
|
||||
|
||||
// set the id for the ref to properly assign in swarm
|
||||
// since swarm needs the ID instead of the name
|
||||
secretRef.SecretID = s.ID
|
||||
addedSecrets = append(addedSecrets, secretRef)
|
||||
}
|
||||
|
||||
return addedSecrets, nil
|
||||
}
|
Loading…
Reference in New Issue