diff --git a/command/swarm/cmd.go b/command/swarm/cmd.go index f0a6bcdeb8..5ea973bb78 100644 --- a/command/swarm/cmd.go +++ b/command/swarm/cmd.go @@ -24,6 +24,7 @@ func NewSwarmCommand(dockerCli *command.DockerCli) *cobra.Command { newJoinTokenCommand(dockerCli), newUpdateCommand(dockerCli), newLeaveCommand(dockerCli), + newUnlockCommand(dockerCli), ) return cmd } diff --git a/command/swarm/init.go b/command/swarm/init.go index 16f372f8d7..b2590e1568 100644 --- a/command/swarm/init.go +++ b/command/swarm/init.go @@ -1,10 +1,15 @@ package swarm import ( + "bufio" + "crypto/rand" "errors" "fmt" + "io" + "math/big" "strings" + "golang.org/x/crypto/ssh/terminal" "golang.org/x/net/context" "github.com/docker/docker/api/types/swarm" @@ -20,6 +25,7 @@ type initOptions struct { // Not a NodeAddrOption because it has no default port. advertiseAddr string forceNewCluster bool + lockKey bool } func newInitCommand(dockerCli *command.DockerCli) *cobra.Command { @@ -39,6 +45,7 @@ func newInitCommand(dockerCli *command.DockerCli) *cobra.Command { flags := cmd.Flags() flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: [:port])") flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: [:port])") + flags.BoolVar(&opts.lockKey, flagLockKey, false, "Encrypt swarm with optionally provided key from stdin") flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state") addSwarmFlags(flags, &opts.swarmOptions) return cmd @@ -48,11 +55,31 @@ func runInit(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts initOption client := dockerCli.Client() ctx := context.Background() + var lockKey string + if opts.lockKey { + var err error + lockKey, err = readKey(dockerCli.In(), "Please enter key for encrypting swarm(leave empty to generate): ") + if err != nil { + return err + } + if len(lockKey) == 0 { + randBytes := make([]byte, 16) + if _, err := rand.Read(randBytes[:]); err != nil { + panic(fmt.Errorf("failed to general random lock key: %v", err)) + } + + var n big.Int + n.SetBytes(randBytes[:]) + lockKey = n.Text(36) + } + } + req := swarm.InitRequest{ ListenAddr: opts.listenAddr.String(), AdvertiseAddr: opts.advertiseAddr, ForceNewCluster: opts.forceNewCluster, Spec: opts.swarmOptions.ToSpec(flags), + LockKey: lockKey, } nodeID, err := client.SwarmInit(ctx, req) @@ -65,6 +92,10 @@ func runInit(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts initOption fmt.Fprintf(dockerCli.Out(), "Swarm initialized: current node (%s) is now a manager.\n\n", nodeID) + if len(lockKey) > 0 { + fmt.Fprintf(dockerCli.Out(), "Swarm is encrypted. When a node is restarted it needs to be unlocked by running command:\n\n echo '%s' | docker swarm unlock\n\n", lockKey) + } + if err := printJoinCommand(ctx, dockerCli, nodeID, true, false); err != nil { return err } @@ -72,3 +103,18 @@ func runInit(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts initOption fmt.Fprint(dockerCli.Out(), "To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.\n\n") return nil } + +func readKey(in *command.InStream, prompt string) (string, error) { + if in.IsTerminal() { + fmt.Print(prompt) + dt, err := terminal.ReadPassword(int(in.FD())) + fmt.Println() + return string(dt), err + } else { + key, err := bufio.NewReader(in).ReadString('\n') + if err == io.EOF { + err = nil + } + return strings.TrimSpace(key), err + } +} diff --git a/command/swarm/opts.go b/command/swarm/opts.go index ce5a9b1de0..a08c761a6d 100644 --- a/command/swarm/opts.go +++ b/command/swarm/opts.go @@ -26,6 +26,7 @@ const ( flagExternalCA = "external-ca" flagMaxSnapshots = "max-snapshots" flagSnapshotInterval = "snapshot-interval" + flagLockKey = "lock-key" ) type swarmOptions struct { diff --git a/command/swarm/unlock.go b/command/swarm/unlock.go new file mode 100644 index 0000000000..03a11da556 --- /dev/null +++ b/command/swarm/unlock.go @@ -0,0 +1,35 @@ +package swarm + +import ( + "context" + + "github.com/spf13/cobra" + + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/cli" + "github.com/docker/docker/cli/command" +) + +func newUnlockCommand(dockerCli *command.DockerCli) *cobra.Command { + cmd := &cobra.Command{ + Use: "unlock", + Short: "Unlock swarm", + Args: cli.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + client := dockerCli.Client() + ctx := context.Background() + + key, err := readKey(dockerCli.In(), "Please enter unlock key: ") + if err != nil { + return err + } + req := swarm.UnlockRequest{ + LockKey: string(key), + } + + return client.SwarmUnlock(ctx, req) + }, + } + + return cmd +} diff --git a/command/system/info.go b/command/system/info.go index 5ea23ed430..da5a396d64 100644 --- a/command/system/info.go +++ b/command/system/info.go @@ -96,7 +96,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { } fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState) - if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive { + if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked { fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID) if info.Swarm.Error != "" { fmt.Fprintf(dockerCli.Out(), " Error: %v\n", info.Swarm.Error)