...

Source file src/edge-infra.dev/pkg/sds/etcd/operator/internal/reconcilers/provision/secret.go

Documentation: edge-infra.dev/pkg/sds/etcd/operator/internal/reconcilers/provision

     1  package provision
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/x509"
     6  	"fmt"
     7  	"path/filepath"
     8  
     9  	"github.com/spf13/afero"
    10  
    11  	"edge-infra.dev/pkg/lib/crypto/certs/pem"
    12  	edgex509 "edge-infra.dev/pkg/lib/crypto/certs/x509"
    13  	"edge-infra.dev/pkg/sds/etcd/operator/internal/resources"
    14  	"edge-infra.dev/pkg/sds/etcd/operator/internal/tar"
    15  	"edge-infra.dev/pkg/sds/lib/etcd/client"
    16  	"edge-infra.dev/pkg/sds/lib/etcd/server"
    17  )
    18  
    19  // secretContent creates the tarball containing the required certificates for etcd
    20  func (r *Reconciler) secretContent(handlers *Handlers) ([]byte, error) {
    21  	ip := handlers.member.EtcdMember.Spec.Address.Host
    22  	certList := []edgex509.CertInfo{
    23  		server.CertInfo(handlers.member.EtcdMember.Name, ip),
    24  		server.PeerCertInfo(handlers.member.EtcdMember.Name, ip),
    25  		client.HealthcheckCertInfo(),
    26  		client.APIServerEtcdCertInfo(),
    27  	}
    28  
    29  	requiredFiles, err := r.requiredFiles(r.Fs, certList, handlers)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  
    34  	writer := tar.NewWriter()
    35  	defer writer.Close()
    36  	// write the required files to the tarball
    37  	if err := writer.Archive(requiredFiles); err != nil {
    38  		return nil, fmt.Errorf("failed to write files to tarball: %w", err)
    39  	}
    40  	return writer.Bytes(), nil
    41  }
    42  
    43  // requiredFiles creates the list of files that must be copied from the controlplane to
    44  // the worker node that the EtcdMember is running on.
    45  func (r *Reconciler) requiredFiles(fs afero.Fs, certList []edgex509.CertInfo, handlers *Handlers) ([]tar.File, error) {
    46  	var requiredFiles []tar.File
    47  	requiredEtcdCerts, err := r.requiredEtcdCerts(fs, certList)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	requiredFiles = append(requiredFiles, requiredEtcdCerts...)
    52  
    53  	requiredKubeFiles, err := r.requiredKubeFiles(fs, handlers.member.EtcdMember.Spec.RequiredFiles)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	return append(requiredFiles, requiredKubeFiles...), nil
    59  }
    60  
    61  // requiredEtcdCerts creates the list of etcd certs that must be copied from the controlplane
    62  // to the worker node that the EtcdMember is running on
    63  func (r *Reconciler) requiredEtcdCerts(fs afero.Fs, certInfoList []edgex509.CertInfo) ([]tar.File, error) {
    64  	caCert, err := etcdCaCert(fs)
    65  	if err != nil {
    66  		return nil, fmt.Errorf("failed to read cert: %s: %w", caCertPath, err)
    67  	}
    68  
    69  	caKeySigner, err := etcdCaKeySigner(fs)
    70  	if err != nil {
    71  		return nil, fmt.Errorf("failed to decode cert: %s: %w", caCertPath, err)
    72  	}
    73  
    74  	var certs []tar.File
    75  	// Create the list of etcd key and cert pairs
    76  	for _, certInfo := range certInfoList {
    77  		certs, err = appendKeyCertPair(certs, certInfo, caCert, caKeySigner)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  	}
    82  
    83  	return certs, nil
    84  }
    85  
    86  // etcdCaCert reads the etcd CA cert from the filesystem
    87  func etcdCaCert(fs afero.Fs) (*x509.Certificate, error) {
    88  	certBytes, err := afero.ReadFile(fs, caCertPath)
    89  	if err != nil {
    90  		return nil, fmt.Errorf("failed to read cert: %s: %w", caCertPath, err)
    91  	}
    92  
    93  	caCert, err := pem.GetCertFromPemFile(certBytes)
    94  	if err != nil {
    95  		return nil, fmt.Errorf("failed to decode cert: %s: %w", caCertPath, err)
    96  	}
    97  
    98  	return caCert, nil
    99  }
   100  
   101  // etcdCaKeySigner gets the etcd CA key from the filesystem and returns a signer
   102  func etcdCaKeySigner(fs afero.Fs) (crypto.Signer, error) {
   103  	keyBytes, err := afero.ReadFile(fs, caKeyPath)
   104  	if err != nil {
   105  		return nil, fmt.Errorf("failed to read key: %s: %w", caKeyPath, err)
   106  	}
   107  
   108  	caKeySigner, err := pem.GetKeySignerFromPemFile(keyBytes)
   109  	if err != nil {
   110  		return nil, fmt.Errorf("failed to decode key: %s: %w", caKeyPath, err)
   111  	}
   112  	return caKeySigner, nil
   113  }
   114  
   115  // appendKeyCertPair appends the key and cert to the list of certs
   116  func appendKeyCertPair(certs []tar.File, certInfo edgex509.CertInfo, caCert *x509.Certificate, caKeySigner crypto.Signer) ([]tar.File, error) {
   117  	encodedKeyPair, err := edgex509.GenerateCertAndKey(certInfo, caCert, caKeySigner)
   118  	if err != nil {
   119  		return nil, fmt.Errorf("failed to create x509 public certificate: %w", err)
   120  	}
   121  
   122  	var path string
   123  	switch certInfo.Name {
   124  	case "apiserver-etcd-client":
   125  		path = "/etc/kubernetes/pki/"
   126  	default:
   127  		path = "/etc/kubernetes/pki/etcd/"
   128  	}
   129  
   130  	key := tar.File{
   131  		Name:  filepath.Join(path, certInfo.Name+".key"),
   132  		Bytes: encodedKeyPair.Key,
   133  		Size:  int64(len(encodedKeyPair.Key)),
   134  		Mode:  0600,
   135  	}
   136  	cert := tar.File{
   137  		Name:  filepath.Join(path, certInfo.Name+".crt"),
   138  		Bytes: encodedKeyPair.Cert,
   139  		Size:  int64(len(encodedKeyPair.Cert)),
   140  		Mode:  0644,
   141  	}
   142  
   143  	return append(certs, key, cert), nil
   144  }
   145  
   146  // requiredKubeFiles creates the list of kubernetes files that must be copied from the
   147  // controlplane to the worker node that the EtcdMember is running on.
   148  func (r *Reconciler) requiredKubeFiles(fs afero.Fs, fileNames []string) ([]tar.File, error) {
   149  	var requiredFiles []tar.File
   150  	for _, fileName := range fileNames {
   151  		file, err := fs.Open(fileName)
   152  		if err != nil {
   153  			return nil, fmt.Errorf("failed to open file: %s: %w", fileName, err)
   154  		}
   155  
   156  		bytes, err := afero.ReadFile(fs, fileName)
   157  		if err != nil {
   158  			return nil, fmt.Errorf("failed to read file: %s: %w", fileName, err)
   159  		}
   160  
   161  		fileInfo, err := file.Stat()
   162  		if err != nil {
   163  			return nil, fmt.Errorf("failed to stat file: %s: %w", fileName, err)
   164  		}
   165  
   166  		requiredFiles = append(requiredFiles, tar.File{
   167  			Name:  fileName,
   168  			Bytes: bytes,
   169  			Size:  fileInfo.Size(),
   170  			Mode:  fileInfo.Mode(),
   171  		})
   172  	}
   173  	return requiredFiles, nil
   174  }
   175  
   176  // generateSecret generates a new Secret object for the EtcdMember
   177  func (r *Reconciler) generateSecret(data []byte, handlers *Handlers) {
   178  	b := resources.NewSecretHandlerBuilder().
   179  		WithClient(r.KubeRetryClient).
   180  		WithKey(handlers.secret.Key).
   181  		HandlesSecret().
   182  		Named(handlers.member.EtcdMember.Name).
   183  		InNamespace(operatorNamespace).
   184  		WithOwner(handlers.member.EtcdMember).
   185  		WithData(data)
   186  	handlers.secret = b.Build()
   187  }
   188  

View as plain text