diff --git a/cmd/cluster.go b/cmd/cluster.go index 2b0d0f0..2db1c84 100644 --- a/cmd/cluster.go +++ b/cmd/cluster.go @@ -16,10 +16,10 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - certutil "k8s.io/client-go/util/cert" "github.com/platform9/cctl/common" "github.com/platform9/cctl/pkg/util/clusterapi" + "github.com/platform9/cctl/pkg/util/secret" "github.com/platform9/cctl/semverutil" spconstants "github.com/platform9/ssh-provider/constants" @@ -43,7 +43,6 @@ var clusterCmdCreate = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { vip := cmd.Flag("vip").Value.String() - // Verify that both routerID and vip are not defaults if one is specified if (routerID == common.RouterID) != (len(vip) == 0) { log.Fatalf("Must specify both routerID and vip, or leave both empty for non-HA cluster.") @@ -85,12 +84,29 @@ var clusterCmdCreate = &cobra.Command{ } } setClusterConfigDefaults(clusterConfig) - newAPIServerCASecret := createCASecret(common.DefaultAPIServerCASecretName, apiServerCACertFile, apiServerCAKeyFile) - newEtcdCASecret := createCASecret(common.DefaultEtcdCASecretName, etcdCACertFile, etcdCAKeyFile) - newFrontProxyCASecret := createCASecret(common.DefaultFrontProxyCASecretName, frontProxyCACertFile, frontProxyCAKeyFile) - newServiceAccountKeySecret := createServiceAccountKeySecret(saPrivateKeyFile, saPublicKeyFile) - newBootstrapTokenSecret := createBootstrapTokenSecret(common.DefaultBootstrapTokenSecretName) + newAPIServerCASecret, err := secret.CreateCASecret(common.DefaultAPIServerCASecretName, apiServerCACertFile, apiServerCAKeyFile) + if err != nil { + log.Fatalf("Unable to generate API Server CA cert pair: %v", err) + } + newEtcdCASecret, err := secret.CreateCASecret(common.DefaultEtcdCASecretName, etcdCACertFile, etcdCAKeyFile) + if err != nil { + log.Fatalf("Unable to generate etcd CA cert pair: %v", err) + } + newFrontProxyCASecret, err := secret.CreateCASecret(common.DefaultFrontProxyCASecretName, frontProxyCACertFile, frontProxyCAKeyFile) + if err != nil { + log.Fatalf("Unable to generate front proxy CA cert pair: %v", err) + } + + newServiceAccountKeySecret, err := secret.CreateSAKeySecret(common.DefaultServiceAccountKeySecretName, saPrivateKeyFile, saPublicKeyFile) + if err != nil { + log.Fatalf("Unable to generate service account key pair: %v", err) + } + newBootstrapTokenSecret, err := secret.CreateBootstrapTokenSecret(common.DefaultBootstrapTokenSecretName) + if err != nil { + log.Fatalf("Unable to generate bootstrap token secret: %v", err) + } + newCluster, err := createCluster(common.DefaultClusterName, podsCIDR, servicesCIDR, vip, routerID, clusterConfig) if err != nil { log.Fatalf("Unable to create cluster: %v", err) @@ -267,105 +283,6 @@ func createCluster(clusterName, podsCIDR, servicesCIDR, vip string, routerID int return &newCluster, nil } -func createServiceAccountKeySecret(saPrivateKeyFile, saPublicKeyFile string) *corev1.Secret { - sakSecret := corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "serviceaccount-key", - Namespace: common.DefaultNamespace, - CreationTimestamp: metav1.Now(), - }, - Data: make(map[string][]byte), - } - - var privateKeyBytes []byte - var publicKeyBytes []byte - if len(saPrivateKeyFile) != 0 && len(saPublicKeyFile) != 0 { - var err error - privateKeyBytes, err = ioutil.ReadFile(saPrivateKeyFile) - if err != nil { - log.Fatalf("Unable to read service account private key %q: %v", saPrivateKeyFile, err) - } - publicKeyBytes, err = ioutil.ReadFile(saPublicKeyFile) - if err != nil { - log.Fatalf("Unable to read service account public key %q: %v", saPublicKeyFile, err) - } - } else { - key, err := certutil.NewPrivateKey() - if err != nil { - log.Fatalf("Unable to create a service account private key: %v", err) - } - privateKeyBytes = certutil.EncodePrivateKeyPEM(key) - publicKeyBytes, err = certutil.EncodePublicKeyPEM(&key.PublicKey) - if err != nil { - log.Fatalf("Unable to encode service account public key to PEM format: %v", err) - } - } - - sakSecret.Data["privatekey"] = privateKeyBytes - sakSecret.Data["publickey"] = publicKeyBytes - - return &sakSecret -} - -func createCASecret(secretName, certFilename, keyFilename string) *corev1.Secret { - caSecret := corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: common.DefaultNamespace, - CreationTimestamp: metav1.Now(), - }, - Data: make(map[string][]byte), - } - - var certBytes []byte - var keyBytes []byte - if len(certFilename) != 0 && len(keyFilename) != 0 { - var err error - certBytes, err = ioutil.ReadFile(certFilename) - if err != nil { - log.Fatalf("Unable to read CA cert %q: %v", certFilename, err) - } - keyBytes, err = ioutil.ReadFile(keyFilename) - if err != nil { - log.Fatalf("Unable to read CA key %q: %v", keyFilename, err) - } - } else { - cert, key, err := common.NewCertificateAuthority() - if err != nil { - log.Fatalf("Unable to create CA: %v", err) - } - certBytes = certutil.EncodeCertPEM(cert) - keyBytes = certutil.EncodePrivateKeyPEM(key) - } - caSecret.Data["tls.crt"] = certBytes - caSecret.Data["tls.key"] = keyBytes - return &caSecret -} - -func createBootstrapTokenSecret(name string) *corev1.Secret { - btSecret := corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: common.DefaultNamespace, - CreationTimestamp: metav1.Now(), - }, - Data: make(map[string][]byte), - } - return &btSecret -} - var clusterCmdDelete = &cobra.Command{ Use: "cluster", Short: "Deletes a node from a cluster", diff --git a/cmd/secret.go b/cmd/secret.go new file mode 100644 index 0000000..72a2998 --- /dev/null +++ b/cmd/secret.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "fmt" + + "github.com/platform9/cctl/common" + log "github.com/platform9/cctl/pkg/logrus" + "github.com/platform9/cctl/pkg/util/secret" + "github.com/spf13/cobra" +) + +var secretCmdCreate = &cobra.Command{ + Use: "secrets", + Short: "Create default secrets", + Run: func(cmd *cobra.Command, args []string) { + err := createSecretDefaults() + if err != nil { + log.Fatalf("Unable to create secrets: %v", err) + } + log.Println("Secrets created successfully.") + }, +} + +func createSecretDefaults() error { + newAPIServerCASecret, err := secret.CreateCASecretDefault(common.DefaultAPIServerCASecretName) + if err != nil { + return fmt.Errorf("unable to generate API server CA secret: %v", err) + } + newEtcdCASecret, err := secret.CreateCASecretDefault(common.DefaultEtcdCASecretName) + if err != nil { + return fmt.Errorf("unable to generate etcd CA secret: %v", err) + } + newFrontProxyCASecret, err := secret.CreateCASecretDefault(common.DefaultFrontProxyCASecretName) + if err != nil { + return fmt.Errorf("unable to generate front proxy CA secret: %v", err) + } + + newServiceAccountKeySecret, err := secret.CreateSAKeySecretDefault(common.DefaultServiceAccountKeySecretName) + if err != nil { + return fmt.Errorf("unable to generate service account CA secret: %v", err) + } + newBootstrapTokenSecret, err := secret.CreateBootstrapTokenSecret(common.DefaultBootstrapTokenSecretName) + if err != nil { + return fmt.Errorf("unable to generate bootstrap token CA secret: %v", err) + } + + if _, err := state.KubeClient.CoreV1().Secrets(common.DefaultNamespace).Create(newAPIServerCASecret); err != nil { + return fmt.Errorf("unable to create API server CA secret: %v", err) + } + if _, err := state.KubeClient.CoreV1().Secrets(common.DefaultNamespace).Create(newEtcdCASecret); err != nil { + return fmt.Errorf("unable to create etcd CA secret: %v", err) + } + if _, err := state.KubeClient.CoreV1().Secrets(common.DefaultNamespace).Create(newFrontProxyCASecret); err != nil { + return fmt.Errorf("unable to create front proxy CA secret: %v", err) + } + if _, err := state.KubeClient.CoreV1().Secrets(common.DefaultNamespace).Create(newServiceAccountKeySecret); err != nil { + return fmt.Errorf("unable to create service account secret: %v", err) + } + if _, err := state.KubeClient.CoreV1().Secrets(common.DefaultNamespace).Create(newBootstrapTokenSecret); err != nil { + return fmt.Errorf("unable to create bootstrap token secret: %v", err) + } + if err := state.PullFromAPIs(); err != nil { + return fmt.Errorf("unable to sync on-disk state: %v", err) + } + + return nil +} + +func init() { + createCmd.AddCommand(secretCmdCreate) +} diff --git a/pkg/util/secret/secret_util.go b/pkg/util/secret/secret_util.go new file mode 100644 index 0000000..d3497f9 --- /dev/null +++ b/pkg/util/secret/secret_util.go @@ -0,0 +1,122 @@ +package secret + +import ( + "fmt" + "io/ioutil" + + "github.com/platform9/cctl/common" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + certutil "k8s.io/client-go/util/cert" +) + +func CreateCASecretDefault(secretName string) (*corev1.Secret, error) { + return CreateCASecret(secretName, "", "") +} + +func CreateSAKeySecretDefault(secretName string) (*corev1.Secret, error) { + return CreateSAKeySecret(secretName, "", "") +} + +func CreateCASecret(secretName, certFilename, keyFilename string) (*corev1.Secret, error) { + caSecret := createSecret(secretName) + + var certBytes []byte + var keyBytes []byte + if len(certFilename) != 0 && len(keyFilename) != 0 { + var err error + certBytes, err = ioutil.ReadFile(certFilename) + if err != nil { + return nil, fmt.Errorf("unable to read CA cert %q: %v", certFilename, err) + } + keyBytes, err = ioutil.ReadFile(keyFilename) + if err != nil { + return nil, fmt.Errorf("unable to read CA key %q: %v", keyFilename, err) + } + } else { + var err error + certBytes, keyBytes, err = generateCertPair() + if err != nil { + return nil, fmt.Errorf("unable to generate cert pair: %v", err) + } + + } + caSecret.Data["tls.crt"] = certBytes + caSecret.Data["tls.key"] = keyBytes + return caSecret, nil +} + +func CreateSAKeySecret(secretName, saPrivateKeyFile, saPublicKeyFile string) (*corev1.Secret, error) { + sakSecret := createSecret(secretName) + + var privateKeyBytes []byte + var publicKeyBytes []byte + if len(saPrivateKeyFile) != 0 && len(saPublicKeyFile) != 0 { + var err error + privateKeyBytes, err = ioutil.ReadFile(saPrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("unable to read service account private key %q: %v", saPrivateKeyFile, err) + } + publicKeyBytes, err = ioutil.ReadFile(saPublicKeyFile) + if err != nil { + return nil, fmt.Errorf("unable to read service account public key %q: %v", saPublicKeyFile, err) + } + } else { + var err error + privateKeyBytes, publicKeyBytes, err = generateKeyPair() + if err != nil { + return nil, fmt.Errorf("unable to generate key pair: %v", err) + } + } + + sakSecret.Data["privatekey"] = privateKeyBytes + sakSecret.Data["publickey"] = publicKeyBytes + + return sakSecret, nil +} + +func CreateBootstrapTokenSecret(secretName string) (*corev1.Secret, error) { + btSecret := createSecret(secretName) + return btSecret, nil +} + +func generateCertPair() ([]byte, []byte, error) { + var certBytes, keyBytes []byte + cert, key, err := common.NewCertificateAuthority() + if err != nil { + return nil, nil, fmt.Errorf("unable to create CA: %v", err) + } + certBytes = certutil.EncodeCertPEM(cert) + keyBytes = certutil.EncodePrivateKeyPEM(key) + return certBytes, keyBytes, nil +} + +func generateKeyPair() ([]byte, []byte, error) { + var privateKeyBytes, publicKeyBytes []byte + key, err := certutil.NewPrivateKey() + if err != nil { + return nil, nil, fmt.Errorf("unable to create a service account private key: %v", err) + } + privateKeyBytes = certutil.EncodePrivateKeyPEM(key) + publicKeyBytes, err = certutil.EncodePublicKeyPEM(&key.PublicKey) + if err != nil { + return nil, nil, fmt.Errorf("unable to encode service account public key to PEM format: %v", err) + } + return privateKeyBytes, publicKeyBytes, nil +} + +func createSecret(name string) *corev1.Secret { + btSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: common.DefaultNamespace, + CreationTimestamp: metav1.Now(), + }, + Data: make(map[string][]byte), + } + return &btSecret +} diff --git a/test/secret_test.go b/test/secret_test.go new file mode 100644 index 0000000..3d08e54 --- /dev/null +++ b/test/secret_test.go @@ -0,0 +1,52 @@ +package test + +import ( + "testing" + + "github.com/platform9/cctl/common" +) + +func TestBTSecret(t *testing.T) { + + btSecret, _ := common.CreateBootstrapTokenSecret("bootstrap") + + if btSecret.ObjectMeta.Name != "bootstrap" { + t.Error("Expected bootstrap, got ", btSecret.ObjectMeta.Name) + } +} + +func TestCASecret(t *testing.T) { + caSecret, err := common.CreateCASecretDefault("apiserver-ca") + if err != nil { + t.Error("Error creating ca secret: ", err) + } + + if caSecret.ObjectMeta.Name != "apiserver-ca" { + t.Error("Expected apiserver-ca, got ", caSecret.ObjectMeta.Name) + } + + if _, ok := caSecret.Data["tls.crt"]; !ok { + t.Error("tls.crt not found.") + } + if _, ok := caSecret.Data["tls.key"]; !ok { + t.Error("tls.key not found") + } +} + +func TestSASecret(t *testing.T) { + saSecret, err := common.CreateSAKeySecretDefault("serviceaccount") + if err != nil { + t.Error("Error creating ca secret: ", err) + } + + if saSecret.ObjectMeta.Name != "serviceaccount" { + t.Error("Expected serviceaccount, got ", saSecret.ObjectMeta.Name) + } + + if _, ok := saSecret.Data["privatekey"]; !ok { + t.Error("privatekey not found.") + } + if _, ok := saSecret.Data["publickey"]; !ok { + t.Error("publickey not found") + } +}