2017-05-08 17:37:36 -04:00
|
|
|
package swarm
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2017-05-08 17:37:36 -04:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2017-06-20 14:41:40 -04:00
|
|
|
"strings"
|
2017-05-08 17:37:36 -04:00
|
|
|
|
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
|
|
|
"github.com/docker/cli/cli/command/swarm/progress"
|
|
|
|
"github.com/docker/docker/api/types/swarm"
|
|
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
2017-06-20 14:41:40 -04:00
|
|
|
"github.com/pkg/errors"
|
2017-05-08 17:37:36 -04:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
)
|
|
|
|
|
|
|
|
type caOptions struct {
|
2017-06-20 14:41:40 -04:00
|
|
|
swarmCAOptions
|
2017-05-08 17:37:36 -04:00
|
|
|
rootCACert PEMFile
|
|
|
|
rootCAKey PEMFile
|
|
|
|
rotate bool
|
|
|
|
detach bool
|
|
|
|
quiet bool
|
|
|
|
}
|
|
|
|
|
2017-06-20 14:41:40 -04:00
|
|
|
func newCACommand(dockerCli command.Cli) *cobra.Command {
|
2017-05-08 17:37:36 -04:00
|
|
|
opts := caOptions{}
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "ca [OPTIONS]",
|
2017-06-20 14:41:40 -04:00
|
|
|
Short: "Display and rotate the root CA",
|
2017-05-08 17:37:36 -04:00
|
|
|
Args: cli.NoArgs,
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2017-06-20 14:41:40 -04:00
|
|
|
return runCA(dockerCli, cmd.Flags(), opts)
|
2017-05-08 17:37:36 -04:00
|
|
|
},
|
2017-10-25 12:59:32 -04:00
|
|
|
Annotations: map[string]string{"version": "1.30"},
|
2017-05-08 17:37:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
2017-06-20 14:41:40 -04:00
|
|
|
addSwarmCAFlags(flags, &opts.swarmCAOptions)
|
2017-05-08 17:37:36 -04:00
|
|
|
flags.BoolVar(&opts.rotate, flagRotate, false, "Rotate the swarm CA - if no certificate or key are provided, new ones will be generated")
|
|
|
|
flags.Var(&opts.rootCACert, flagCACert, "Path to the PEM-formatted root CA certificate to use for the new cluster")
|
|
|
|
flags.Var(&opts.rootCAKey, flagCAKey, "Path to the PEM-formatted root CA key to use for the new cluster")
|
|
|
|
|
|
|
|
flags.BoolVarP(&opts.detach, "detach", "d", false, "Exit immediately instead of waiting for the root rotation to converge")
|
|
|
|
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress progress output")
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2017-06-20 14:41:40 -04:00
|
|
|
func runCA(dockerCli command.Cli, flags *pflag.FlagSet, opts caOptions) error {
|
2017-05-08 17:37:36 -04:00
|
|
|
client := dockerCli.Client()
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
swarmInspect, err := client.SwarmInspect(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !opts.rotate {
|
2017-07-12 14:44:47 -04:00
|
|
|
for _, f := range []string{flagCACert, flagCAKey, flagCertExpiry, flagExternalCA} {
|
2017-06-19 16:34:18 -04:00
|
|
|
if flags.Changed(f) {
|
|
|
|
return fmt.Errorf("`--%s` flag requires the `--rotate` flag to update the CA", f)
|
|
|
|
}
|
|
|
|
}
|
2017-06-20 14:41:40 -04:00
|
|
|
return displayTrustRoot(dockerCli.Out(), swarmInspect)
|
2017-05-08 17:37:36 -04:00
|
|
|
}
|
|
|
|
|
Propagate the provided external CA certificate to the external CA object
in swarm.
Also, fix some CLI command confusions:
1. If the --external-ca flag is provided, require a --ca-cert flag as well, otherwise
the external CA is set but the CA certificate is actually rotated to an internal
cert
2. If a --ca-cert flag is provided, require a --ca-key or --external-ca flag be
provided as well, otherwise either the server will say that the request is
invalid, or if there was previously an external CA corresponding to the cert, it
will succeed. While that works, it's better to require the user to explicitly
set all the parameters of the new desired root CA.
This also changes the `swarm update` function to set the external CA's CACert field,
which while not strictly necessary, makes the CA list more explicit.
Signed-off-by: Ying Li <ying.li@docker.com>
2018-07-02 19:53:23 -04:00
|
|
|
if flags.Changed(flagExternalCA) && len(opts.externalCA.Value()) > 0 && !flags.Changed(flagCACert) {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"rotating to an external CA requires the `--%s` flag to specify the external CA's cert - "+
|
|
|
|
"to add an external CA with the current root CA certificate, use the `update` command instead", flagCACert)
|
|
|
|
}
|
|
|
|
|
|
|
|
if flags.Changed(flagCACert) && len(opts.externalCA.Value()) == 0 && !flags.Changed(flagCAKey) {
|
|
|
|
return fmt.Errorf("the --%s flag requires that a --%s flag and/or --%s flag be provided as well",
|
|
|
|
flagCACert, flagCAKey, flagExternalCA)
|
|
|
|
}
|
|
|
|
|
2017-06-20 14:41:40 -04:00
|
|
|
updateSwarmSpec(&swarmInspect.Spec, flags, opts)
|
2017-05-08 17:37:36 -04:00
|
|
|
if err := client.SwarmUpdate(ctx, swarmInspect.Version, swarmInspect.Spec, swarm.UpdateFlags{}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.detach {
|
|
|
|
return nil
|
|
|
|
}
|
2017-06-20 14:41:40 -04:00
|
|
|
return attach(ctx, dockerCli, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, opts caOptions) {
|
|
|
|
caCert := opts.rootCACert.Contents()
|
|
|
|
caKey := opts.rootCAKey.Contents()
|
Propagate the provided external CA certificate to the external CA object
in swarm.
Also, fix some CLI command confusions:
1. If the --external-ca flag is provided, require a --ca-cert flag as well, otherwise
the external CA is set but the CA certificate is actually rotated to an internal
cert
2. If a --ca-cert flag is provided, require a --ca-key or --external-ca flag be
provided as well, otherwise either the server will say that the request is
invalid, or if there was previously an external CA corresponding to the cert, it
will succeed. While that works, it's better to require the user to explicitly
set all the parameters of the new desired root CA.
This also changes the `swarm update` function to set the external CA's CACert field,
which while not strictly necessary, makes the CA list more explicit.
Signed-off-by: Ying Li <ying.li@docker.com>
2018-07-02 19:53:23 -04:00
|
|
|
opts.mergeSwarmSpecCAFlags(spec, flags, caCert)
|
|
|
|
|
|
|
|
spec.CAConfig.SigningCACert = caCert
|
|
|
|
spec.CAConfig.SigningCAKey = caKey
|
2017-06-20 14:41:40 -04:00
|
|
|
|
|
|
|
if caKey == "" && caCert == "" {
|
|
|
|
spec.CAConfig.ForceRotate++
|
|
|
|
}
|
|
|
|
}
|
2017-05-08 17:37:36 -04:00
|
|
|
|
2017-06-20 14:41:40 -04:00
|
|
|
func attach(ctx context.Context, dockerCli command.Cli, opts caOptions) error {
|
|
|
|
client := dockerCli.Client()
|
2017-05-08 17:37:36 -04:00
|
|
|
errChan := make(chan error, 1)
|
|
|
|
pipeReader, pipeWriter := io.Pipe()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
errChan <- progress.RootRotationProgress(ctx, client, pipeWriter)
|
|
|
|
}()
|
|
|
|
|
|
|
|
if opts.quiet {
|
2017-05-16 17:32:29 -04:00
|
|
|
go io.Copy(ioutil.Discard, pipeReader)
|
2017-05-08 17:37:36 -04:00
|
|
|
return <-errChan
|
|
|
|
}
|
|
|
|
|
2017-06-20 14:41:40 -04:00
|
|
|
err := jsonmessage.DisplayJSONMessagesToStream(pipeReader, dockerCli.Out(), nil)
|
2017-05-08 17:37:36 -04:00
|
|
|
if err == nil {
|
|
|
|
err = <-errChan
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-06-20 14:41:40 -04:00
|
|
|
swarmInspect, err := client.SwarmInspect(ctx)
|
2017-05-08 17:37:36 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-20 14:41:40 -04:00
|
|
|
return displayTrustRoot(dockerCli.Out(), swarmInspect)
|
|
|
|
}
|
2017-05-08 17:37:36 -04:00
|
|
|
|
2017-06-20 14:41:40 -04:00
|
|
|
func displayTrustRoot(out io.Writer, info swarm.Swarm) error {
|
|
|
|
if info.ClusterInfo.TLSInfo.TrustRoot == "" {
|
|
|
|
return errors.New("No CA information available")
|
2017-05-08 17:37:36 -04:00
|
|
|
}
|
2017-06-20 14:41:40 -04:00
|
|
|
fmt.Fprintln(out, strings.TrimSpace(info.ClusterInfo.TLSInfo.TrustRoot))
|
2017-05-08 17:37:36 -04:00
|
|
|
return nil
|
|
|
|
}
|