mirror of https://github.com/docker/cli.git
Revise swarm init/update flags, add unlocking capability
- Neither swarm init or swarm update should take an unlock key - Add an autolock flag to turn on autolock - Make the necessary docker api changes - Add SwarmGetUnlockKey API call and use it when turning on autolock - Add swarm unlock-key subcommand Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
d006a04357
commit
56b7ad90b1
|
@ -22,6 +22,7 @@ func NewSwarmCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
newInitCommand(dockerCli),
|
||||
newJoinCommand(dockerCli),
|
||||
newJoinTokenCommand(dockerCli),
|
||||
newUnlockKeyCommand(dockerCli),
|
||||
newUpdateCommand(dockerCli),
|
||||
newLeaveCommand(dockerCli),
|
||||
newUnlockCommand(dockerCli),
|
||||
|
|
|
@ -1,20 +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"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
@ -25,7 +20,6 @@ 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 {
|
||||
|
@ -45,7 +39,6 @@ func newInitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
flags := cmd.Flags()
|
||||
flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
|
||||
flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[: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
|
||||
|
@ -55,31 +48,12 @@ 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,
|
||||
AutoLockManagers: opts.swarmOptions.autolock,
|
||||
}
|
||||
|
||||
nodeID, err := client.SwarmInit(ctx, req)
|
||||
|
@ -92,29 +66,19 @@ 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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if req.AutoLockManagers {
|
||||
unlockKeyResp, err := client.SwarmGetUnlockKey(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch unlock key")
|
||||
}
|
||||
printUnlockCommand(ctx, dockerCli, unlockKeyResp.UnlockKey)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ const (
|
|||
flagMaxSnapshots = "max-snapshots"
|
||||
flagSnapshotInterval = "snapshot-interval"
|
||||
flagLockKey = "lock-key"
|
||||
flagAutolock = "autolock"
|
||||
)
|
||||
|
||||
type swarmOptions struct {
|
||||
|
@ -36,6 +37,7 @@ type swarmOptions struct {
|
|||
externalCA ExternalCAOption
|
||||
maxSnapshots uint64
|
||||
snapshotInterval uint64
|
||||
autolock bool
|
||||
}
|
||||
|
||||
// NodeAddrOption is a pflag.Value for listening addresses
|
||||
|
@ -174,6 +176,7 @@ func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
|
|||
flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
|
||||
flags.Uint64Var(&opts.maxSnapshots, flagMaxSnapshots, 0, "Number of additional Raft snapshots to retain")
|
||||
flags.Uint64Var(&opts.snapshotInterval, flagSnapshotInterval, 10000, "Number of log entries between Raft snapshots")
|
||||
flags.BoolVar(&opts.autolock, flagAutolock, false, "Enable or disable manager autolocking (requiring an unlock key to start a stopped manager)")
|
||||
}
|
||||
|
||||
func (opts *swarmOptions) mergeSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet) {
|
||||
|
@ -195,6 +198,9 @@ func (opts *swarmOptions) mergeSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet)
|
|||
if flags.Changed(flagSnapshotInterval) {
|
||||
spec.Raft.SnapshotInterval = opts.snapshotInterval
|
||||
}
|
||||
if flags.Changed(flagAutolock) {
|
||||
spec.EncryptionConfig.AutoLockManagers = opts.autolock
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *swarmOptions) ToSpec(flags *pflag.FlagSet) swarm.Spec {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
|
@ -24,7 +29,7 @@ func newUnlockCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
return err
|
||||
}
|
||||
req := swarm.UnlockRequest{
|
||||
LockKey: string(key),
|
||||
UnlockKey: key,
|
||||
}
|
||||
|
||||
return client.SwarmUnlock(ctx, req)
|
||||
|
@ -33,3 +38,17 @@ func newUnlockCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||
|
||||
return cmd
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
key, err := bufio.NewReader(in).ReadString('\n')
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return strings.TrimSpace(key), err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newUnlockKeyCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var rotate, quiet bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "unlock-key [OPTIONS]",
|
||||
Short: "Manage the unlock key",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
if rotate {
|
||||
// FIXME(aaronl)
|
||||
}
|
||||
|
||||
unlockKeyResp, err := client.SwarmGetUnlockKey(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch unlock key")
|
||||
}
|
||||
|
||||
if quiet {
|
||||
fmt.Fprintln(dockerCli.Out(), unlockKeyResp.UnlockKey)
|
||||
} else {
|
||||
printUnlockCommand(ctx, dockerCli, unlockKeyResp.UnlockKey)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&rotate, flagRotate, false, "Rotate unlock key")
|
||||
flags.BoolVarP(&quiet, flagQuiet, "q", false, "Only display token")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func printUnlockCommand(ctx context.Context, dockerCli *command.DockerCli, unlockKey string) {
|
||||
if len(unlockKey) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "To unlock a swarm manager after it restarts, run the `docker swarm unlock`\ncommand and provide the following key:\n\n %s\n\nPlease remember to store this key in a password manager, since without it you\nwill not be able to restart the manager.\n", unlockKey)
|
||||
return
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
@ -39,8 +40,12 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts swarmOpt
|
|||
return err
|
||||
}
|
||||
|
||||
prevAutoLock := swarm.Spec.EncryptionConfig.AutoLockManagers
|
||||
|
||||
opts.mergeSwarmSpec(&swarm.Spec, flags)
|
||||
|
||||
curAutoLock := swarm.Spec.EncryptionConfig.AutoLockManagers
|
||||
|
||||
err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec, updateFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -48,5 +53,13 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts swarmOpt
|
|||
|
||||
fmt.Fprintln(dockerCli.Out(), "Swarm updated.")
|
||||
|
||||
if curAutoLock && !prevAutoLock {
|
||||
unlockKeyResp, err := client.SwarmGetUnlockKey(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch unlock key")
|
||||
}
|
||||
printUnlockCommand(ctx, dockerCli, unlockKeyResp.UnlockKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue