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 }