1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package physicalmachine
17
18 import (
19 "context"
20 "crypto"
21 "crypto/x509"
22 "os"
23 "path/filepath"
24 "strconv"
25
26 "github.com/pkg/errors"
27 "github.com/spf13/cobra"
28 v1 "k8s.io/api/core/v1"
29 "k8s.io/apimachinery/pkg/types"
30 "k8s.io/client-go/util/keyutil"
31 "sigs.k8s.io/controller-runtime/pkg/client"
32
33 "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/common"
34 "github.com/chaos-mesh/chaos-mesh/pkg/label"
35 )
36
37 type PhysicalMachineInitOptions struct {
38 chaosMeshNamespace string
39 remoteIP string
40 sshUser string
41 sshPort int
42 sshPrivateKeyFile string
43 chaosdPort int
44 outputPath string
45 namespace string
46 labels string
47 }
48
49 func NewPhysicalMachineInitCmd() (*cobra.Command, error) {
50 initOption := &PhysicalMachineInitOptions{}
51
52 initCmd := &cobra.Command{
53 Use: `init (PHYSICALMACHINE_NAME) [-n NAMESPACE]`,
54 Short: `Generate TLS certs for certain physical machine automatically, and create PhysicalMachine CustomResource in Kubernetes cluster`,
55 Long: `Generate TLS certs for certain physical machine automatically, and create PhysicalMachine CustomResource in Kubernetes cluster
56
57 Examples:
58 # Generate TLS certs for remote physical machine, and create PhysicalMachine CustomResource in certain namespace
59 chaosctl pm init PHYSICALMACHINE_NAME -n NAMESPACE --ip REMOTEIP
60
61 # Generate TLS certs for remote physical machine, and create PhysicalMachine CustomResource in certain namespace with specified labels
62 chaosctl pm init PHYSICALMACHINE_NAME -n NAMESPACE --ip REMOTEIP -l key1=value1,key2=value2
63 `,
64 SilenceErrors: true,
65 SilenceUsage: true,
66 RunE: func(cmd *cobra.Command, args []string) error {
67 if err := initOption.Validate(); err != nil {
68 return err
69 }
70 return initOption.Run(args)
71 },
72 }
73 initCmd.PersistentFlags().StringVar(&initOption.chaosMeshNamespace, "chaos-mesh-namespace", "chaos-mesh", "namespace where chaos mesh installed")
74 initCmd.PersistentFlags().StringVar(&initOption.remoteIP, "ip", "", "ip of the remote physical machine")
75 initCmd.PersistentFlags().StringVar(&initOption.sshUser, "ssh-user", "root", "username for ssh connection")
76 initCmd.PersistentFlags().StringVar(&initOption.sshPrivateKeyFile, "ssh-key", filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa"), "private key filepath for ssh connection")
77 initCmd.PersistentFlags().IntVar(&initOption.sshPort, "ssh-port", 22, "port of ssh connection")
78 initCmd.PersistentFlags().IntVar(&initOption.chaosdPort, "chaosd-port", 31768, "port of the remote chaosd server listen")
79 initCmd.PersistentFlags().StringVar(&initOption.outputPath, "path", "/etc/chaosd/pki", "path to save generated certs")
80 initCmd.PersistentFlags().StringVarP(&initOption.namespace, "namespace", "n", "default", "namespace of the certain physical machine")
81 initCmd.PersistentFlags().StringVarP(&initOption.labels, "labels", "l", "", "labels of the certain physical machine (e.g. -l key1=value1,key2=value2)")
82 return initCmd, nil
83 }
84
85 func (o *PhysicalMachineInitOptions) Validate() error {
86 if len(o.remoteIP) == 0 {
87 return errors.New("--ip must be specified")
88 }
89 return nil
90 }
91
92 func (o *PhysicalMachineInitOptions) Run(args []string) error {
93 if len(args) < 1 {
94 return errors.New("physical machine name is required")
95 }
96 physicalMachineName := args[0]
97
98 labels, err := label.ParseLabel(o.labels)
99 if err != nil {
100 return err
101 }
102 address := formatAddress(o.remoteIP, o.chaosdPort, true)
103
104 clientset, err := common.InitClientSet()
105 if err != nil {
106 return err
107 }
108
109 ctx := context.Background()
110 caCert, caKey, err := GetChaosdCAFileFromCluster(ctx, o.chaosMeshNamespace, clientset.CtrlCli)
111 if err != nil {
112 return err
113 }
114
115
116 serverCert, serverKey, err := NewCertAndKey(caCert, caKey)
117 if err != nil {
118 return err
119 }
120
121 sshTunnel, err := NewSshTunnel(o.remoteIP, strconv.Itoa(o.sshPort), o.sshUser, o.sshPrivateKeyFile)
122 if err != nil {
123 return err
124 }
125 if err := sshTunnel.Open(); err != nil {
126 return err
127 }
128 defer sshTunnel.Close()
129
130 if err := writeCertAndKeyToRemote(sshTunnel, o.outputPath, ChaosdPkiName, serverCert, serverKey); err != nil {
131 return err
132 }
133 if err := writeCertToRemote(sshTunnel, o.outputPath, "ca", caCert); err != nil {
134 return err
135 }
136
137 return CreatePhysicalMachine(ctx, clientset.CtrlCli, o.namespace, physicalMachineName, address, labels)
138 }
139
140 func GetChaosdCAFileFromCluster(ctx context.Context, namespace string, c client.Client) (caCert *x509.Certificate, caKey crypto.Signer, err error) {
141 var secret v1.Secret
142 if err := c.Get(ctx, types.NamespacedName{
143 Namespace: namespace,
144 Name: "chaos-mesh-chaosd-client-certs",
145 }, &secret); err != nil {
146 return nil, nil, errors.Wrapf(err, "could not found secret `chaos-mesh-chaosd-client-certs` in namespace %s", namespace)
147 }
148
149 var caCertBytes []byte
150 var ok bool
151 if caCertBytes, ok = secret.Data["ca.crt"]; !ok {
152 return nil, nil, errors.New("could not found ca cert file in `chaos-mesh-chaosd-client-certs` secret")
153 }
154
155 var caKeyBytes []byte
156 if caKeyBytes, ok = secret.Data["ca.key"]; !ok {
157 return nil, nil, errors.New("could not found ca key file in `chaos-mesh-chaosd-client-certs` secret")
158 }
159
160 return ParseCertAndKey(caCertBytes, caKeyBytes)
161 }
162
163 func writeCertAndKeyToRemote(sshTunnel *SshTunnel, pkiPath, pkiName string, cert *x509.Certificate, key crypto.Signer) error {
164 keyBytes, err := keyutil.MarshalPrivateKeyToPEM(key)
165 if err != nil {
166 return err
167 }
168 if err := writeCertToRemote(sshTunnel, pkiPath, pkiName, cert); err != nil {
169 return err
170 }
171 return sshTunnel.SFTP(pathForKey(pkiPath, pkiName), keyBytes)
172 }
173
174 func writeCertToRemote(sshTunnel *SshTunnel, pkiPath, pkiName string, cert *x509.Certificate) error {
175 return sshTunnel.SFTP(pathForCert(pkiPath, pkiName), EncodeCertPEM(cert))
176 }
177