github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/command/swarm/ca.go (about)

     1  package swarm
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"strings"
     9  
    10  	"github.com/docker/cli/cli"
    11  	"github.com/docker/cli/cli/command"
    12  	"github.com/docker/cli/cli/command/swarm/progress"
    13  	"github.com/docker/docker/api/types/swarm"
    14  	"github.com/docker/docker/pkg/jsonmessage"
    15  	"github.com/pkg/errors"
    16  	"github.com/spf13/cobra"
    17  	"github.com/spf13/pflag"
    18  )
    19  
    20  type caOptions struct {
    21  	swarmCAOptions
    22  	rootCACert PEMFile
    23  	rootCAKey  PEMFile
    24  	rotate     bool
    25  	detach     bool
    26  	quiet      bool
    27  }
    28  
    29  func newCACommand(dockerCli command.Cli) *cobra.Command {
    30  	opts := caOptions{}
    31  
    32  	cmd := &cobra.Command{
    33  		Use:   "ca [OPTIONS]",
    34  		Short: "Display and rotate the root CA",
    35  		Args:  cli.NoArgs,
    36  		RunE: func(cmd *cobra.Command, args []string) error {
    37  			return runCA(dockerCli, cmd.Flags(), opts)
    38  		},
    39  		Annotations: map[string]string{"version": "1.30"},
    40  	}
    41  
    42  	flags := cmd.Flags()
    43  	addSwarmCAFlags(flags, &opts.swarmCAOptions)
    44  	flags.BoolVar(&opts.rotate, flagRotate, false, "Rotate the swarm CA - if no certificate or key are provided, new ones will be generated")
    45  	flags.Var(&opts.rootCACert, flagCACert, "Path to the PEM-formatted root CA certificate to use for the new cluster")
    46  	flags.Var(&opts.rootCAKey, flagCAKey, "Path to the PEM-formatted root CA key to use for the new cluster")
    47  
    48  	flags.BoolVarP(&opts.detach, "detach", "d", false, "Exit immediately instead of waiting for the root rotation to converge")
    49  	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress progress output")
    50  	return cmd
    51  }
    52  
    53  func runCA(dockerCli command.Cli, flags *pflag.FlagSet, opts caOptions) error {
    54  	client := dockerCli.Client()
    55  	ctx := context.Background()
    56  
    57  	swarmInspect, err := client.SwarmInspect(ctx)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	if !opts.rotate {
    63  		for _, f := range []string{flagCACert, flagCAKey, flagCertExpiry, flagExternalCA} {
    64  			if flags.Changed(f) {
    65  				return fmt.Errorf("`--%s` flag requires the `--rotate` flag to update the CA", f)
    66  			}
    67  		}
    68  		return displayTrustRoot(dockerCli.Out(), swarmInspect)
    69  	}
    70  
    71  	if flags.Changed(flagExternalCA) && len(opts.externalCA.Value()) > 0 && !flags.Changed(flagCACert) {
    72  		return fmt.Errorf(
    73  			"rotating to an external CA requires the `--%s` flag to specify the external CA's cert - "+
    74  				"to add an external CA with the current root CA certificate, use the `update` command instead", flagCACert)
    75  	}
    76  
    77  	if flags.Changed(flagCACert) && len(opts.externalCA.Value()) == 0 && !flags.Changed(flagCAKey) {
    78  		return fmt.Errorf("the --%s flag requires that a --%s flag and/or --%s flag be provided as well",
    79  			flagCACert, flagCAKey, flagExternalCA)
    80  	}
    81  
    82  	updateSwarmSpec(&swarmInspect.Spec, flags, opts)
    83  	if err := client.SwarmUpdate(ctx, swarmInspect.Version, swarmInspect.Spec, swarm.UpdateFlags{}); err != nil {
    84  		return err
    85  	}
    86  
    87  	if opts.detach {
    88  		return nil
    89  	}
    90  	return attach(ctx, dockerCli, opts)
    91  }
    92  
    93  func updateSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, opts caOptions) {
    94  	caCert := opts.rootCACert.Contents()
    95  	caKey := opts.rootCAKey.Contents()
    96  	opts.mergeSwarmSpecCAFlags(spec, flags, caCert)
    97  
    98  	spec.CAConfig.SigningCACert = caCert
    99  	spec.CAConfig.SigningCAKey = caKey
   100  
   101  	if caKey == "" && caCert == "" {
   102  		spec.CAConfig.ForceRotate++
   103  	}
   104  }
   105  
   106  func attach(ctx context.Context, dockerCli command.Cli, opts caOptions) error {
   107  	client := dockerCli.Client()
   108  	errChan := make(chan error, 1)
   109  	pipeReader, pipeWriter := io.Pipe()
   110  
   111  	go func() {
   112  		errChan <- progress.RootRotationProgress(ctx, client, pipeWriter)
   113  	}()
   114  
   115  	if opts.quiet {
   116  		go io.Copy(ioutil.Discard, pipeReader)
   117  		return <-errChan
   118  	}
   119  
   120  	err := jsonmessage.DisplayJSONMessagesToStream(pipeReader, dockerCli.Out(), nil)
   121  	if err == nil {
   122  		err = <-errChan
   123  	}
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	swarmInspect, err := client.SwarmInspect(ctx)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	return displayTrustRoot(dockerCli.Out(), swarmInspect)
   133  }
   134  
   135  func displayTrustRoot(out io.Writer, info swarm.Swarm) error {
   136  	if info.ClusterInfo.TLSInfo.TrustRoot == "" {
   137  		return errors.New("No CA information available")
   138  	}
   139  	fmt.Fprintln(out, strings.TrimSpace(info.ClusterInfo.TLSInfo.TrustRoot))
   140  	return nil
   141  }