github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/swarm/ca.go (about)

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