...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/physicalmachine/init.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/physicalmachine

     1  // Copyright 2021 Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    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  	// generate chaosd cert and private key
   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