2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-03-22 11:03:15 -04:00
package cmd
import (
2022-06-21 11:59:12 -04:00
"context"
2022-11-24 04:57:58 -05:00
"encoding/hex"
2022-11-25 04:02:12 -05:00
"errors"
2022-03-22 11:03:15 -04:00
"fmt"
"io"
"net"
2023-02-10 08:59:44 -05:00
"os"
2022-05-13 07:10:27 -04:00
"strconv"
2023-03-03 03:38:57 -05:00
"sync"
2022-04-05 03:13:09 -04:00
"text/tabwriter"
2022-06-21 11:59:12 -04:00
"time"
2022-03-22 11:03:15 -04:00
2023-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2023-05-03 05:11:53 -04:00
"github.com/edgelesssys/constellation/v2/internal/atls"
2023-06-09 09:41:02 -04:00
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
2023-02-13 05:54:38 -05:00
"github.com/edgelesssys/constellation/v2/internal/compatibility"
2023-05-02 03:35:52 -04:00
"github.com/spf13/afero"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
clientcodec "k8s.io/client-go/tools/clientcmd/api/latest"
"sigs.k8s.io/yaml"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
2022-10-11 06:24:33 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
2022-11-09 08:43:48 -05:00
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
2022-11-09 08:43:48 -05:00
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
2023-03-17 04:43:22 -04:00
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/crypto"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
2023-06-12 07:45:34 -04:00
"github.com/edgelesssys/constellation/v2/internal/grpc/grpclog"
2022-09-21 07:47:57 -04:00
grpcRetry "github.com/edgelesssys/constellation/v2/internal/grpc/retry"
2023-03-02 09:08:31 -05:00
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/edgelesssys/constellation/v2/internal/retry"
"github.com/edgelesssys/constellation/v2/internal/versions"
2022-03-22 11:03:15 -04:00
)
2022-06-08 02:14:28 -04:00
// NewInitCmd returns a new cobra.Command for the init command.
func NewInitCmd ( ) * cobra . Command {
2022-03-22 11:03:15 -04:00
cmd := & cobra . Command {
2022-09-07 09:38:29 -04:00
Use : "init" ,
Short : "Initialize the Constellation cluster" ,
2023-01-17 08:01:56 -05:00
Long : "Initialize the Constellation cluster.\n\n" +
"Start your confidential Kubernetes." ,
Args : cobra . ExactArgs ( 0 ) ,
RunE : runInitialize ,
2022-03-22 11:03:15 -04:00
}
2022-05-09 11:02:47 -04:00
cmd . Flags ( ) . String ( "master-secret" , "" , "path to base64-encoded master secret" )
2022-09-20 04:07:55 -04:00
cmd . Flags ( ) . Bool ( "conformance" , false , "enable conformance mode" )
2023-02-10 08:59:44 -05:00
cmd . Flags ( ) . Bool ( "merge-kubeconfig" , false , "merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config" )
2022-03-22 11:03:15 -04:00
return cmd
}
2023-01-04 04:46:29 -05:00
type initCmd struct {
2023-05-30 07:47:36 -04:00
log debugLog
merger configMerger
spinner spinnerInterf
masterSecret uri . MasterSecret
fh * file . Handler
2023-01-04 04:46:29 -05:00
}
2022-03-22 11:03:15 -04:00
// runInitialize runs the initialize command.
2023-03-20 06:03:36 -04:00
func runInitialize ( cmd * cobra . Command , _ [ ] string ) error {
2023-01-04 04:46:29 -05:00
log , err := newCLILogger ( cmd )
if err != nil {
return fmt . Errorf ( "creating logger: %w" , err )
}
defer log . Sync ( )
2022-03-22 11:03:15 -04:00
fileHandler := file . NewHandler ( afero . NewOsFs ( ) )
2023-05-03 05:11:53 -04:00
newDialer := func ( validator atls . Validator ) * dialer . Dialer {
return dialer . New ( nil , validator , & net . Dialer { } )
2022-08-09 08:04:40 -04:00
}
2022-10-31 14:25:02 -04:00
2023-01-18 07:10:24 -05:00
spinner , err := newSpinnerOrStderr ( cmd )
if err != nil {
return err
}
2022-10-07 13:35:07 -04:00
defer spinner . Stop ( )
2022-09-05 12:12:46 -04:00
ctx , cancel := context . WithTimeout ( cmd . Context ( ) , time . Hour )
defer cancel ( )
cmd . SetContext ( ctx )
2023-05-30 07:47:36 -04:00
i := & initCmd { log : log , spinner : spinner , merger : & kubeconfigMerger { log : log } , fh : & fileHandler }
2023-06-07 10:16:32 -04:00
fetcher := attestationconfigapi . NewFetcher ( )
2023-06-01 07:55:46 -04:00
return i . initialize ( cmd , newDialer , fileHandler , license . NewClient ( ) , fetcher )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
// initialize initializes a Constellation.
2023-05-03 05:11:53 -04:00
func ( i * initCmd ) initialize ( cmd * cobra . Command , newDialer func ( validator atls . Validator ) * dialer . Dialer ,
2023-06-07 10:16:32 -04:00
fileHandler file . Handler , quotaChecker license . QuotaChecker , configFetcher attestationconfigapi . Fetcher ,
2022-03-22 11:03:15 -04:00
) error {
2023-01-04 04:46:29 -05:00
flags , err := i . evalFlagArgs ( cmd )
2022-04-13 09:01:02 -04:00
if err != nil {
return err
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Using flags: %+v" , flags )
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Loading configuration file from %q" , flags . configPath )
2023-06-01 07:55:46 -04:00
conf , err := config . New ( fileHandler , flags . configPath , configFetcher , flags . force )
2023-02-07 06:56:25 -05:00
var configValidationErr * config . ValidationError
if errors . As ( err , & configValidationErr ) {
cmd . PrintErrln ( configValidationErr . LongMessage ( ) )
}
2022-03-22 11:03:15 -04:00
if err != nil {
2023-02-07 06:56:25 -05:00
return err
2022-03-22 11:03:15 -04:00
}
2023-06-27 12:24:35 -04:00
if ! flags . force {
if err := validateCLIandConstellationVersionAreEqual ( constants . VersionInfo ( ) , conf . Image , conf . MicroserviceVersion ) ; err != nil {
return err
}
}
2023-06-09 09:41:02 -04:00
if conf . GetAttestationConfig ( ) . GetVariant ( ) . Equal ( variant . AWSSEVSNP { } ) {
2023-07-05 10:44:57 -04:00
cmd . PrintErrln ( "WARNING: Attestation temporarily relies on AWS nitroTPM. See https://docs.edgeless.systems/constellation/workflows/config#choosing-a-vm-type for more information." )
2023-06-09 09:41:02 -04:00
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Checking cluster ID file" )
2022-10-11 06:24:33 -04:00
var idFile clusterid . File
if err := fileHandler . ReadJSON ( constants . ClusterIDsFileName , & idFile ) ; err != nil {
return fmt . Errorf ( "reading cluster ID file: %w" , err )
}
2023-06-05 03:13:02 -04:00
// config validation does not check k8s patch version since upgrade may accept an outdated patch version.
// init only supported up-to-date versions.
k8sVersion , err := versions . NewValidK8sVersion ( compatibility . EnsurePrefixV ( conf . KubernetesVersion ) , true )
2022-09-02 08:05:44 -04:00
if err != nil {
2023-06-05 03:13:02 -04:00
return fmt . Errorf ( "invalid Kubernetes version: %s" , conf . KubernetesVersion )
2022-09-02 08:05:44 -04:00
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Validated k8s version as %s" , k8sVersion )
2022-09-02 08:05:44 -04:00
if versions . IsPreviewK8sVersion ( k8sVersion ) {
2022-11-10 04:27:24 -05:00
cmd . PrintErrf ( "Warning: Constellation with Kubernetes %v is still in preview. Use only for evaluation purposes.\n" , k8sVersion )
2022-09-02 08:05:44 -04:00
}
2022-11-15 09:40:49 -05:00
provider := conf . GetProvider ( )
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Got provider %s" , provider . String ( ) )
2022-08-25 08:06:29 -04:00
checker := license . NewChecker ( quotaChecker , fileHandler )
2022-11-15 09:40:49 -05:00
if err := checker . CheckLicense ( cmd . Context ( ) , provider , conf . Provider , cmd . Printf ) ; err != nil {
2022-11-10 04:27:24 -05:00
cmd . PrintErrf ( "License check failed: %v" , err )
2022-08-16 10:06:38 -04:00
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Checked license" )
2023-03-21 07:46:49 -04:00
2023-05-03 05:11:53 -04:00
conf . UpdateMAAURL ( idFile . AttestationURL )
i . log . Debugf ( "Creating aTLS Validator for %s" , conf . GetAttestationConfig ( ) . GetVariant ( ) )
validator , err := cloudcmd . NewValidator ( cmd , conf . GetAttestationConfig ( ) , i . log )
2022-04-19 11:02:02 -04:00
if err != nil {
2023-03-22 11:53:01 -04:00
return fmt . Errorf ( "creating new validator: %w" , err )
2022-03-22 11:03:15 -04:00
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Created a new validator" )
serviceAccURI , err := i . getMarshaledServiceAccountURI ( provider , conf , fileHandler )
2022-08-25 09:12:08 -04:00
if err != nil {
return err
2022-03-22 11:03:15 -04:00
}
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Successfully marshaled service account URI" )
2023-01-04 04:46:29 -05:00
masterSecret , err := i . readOrGenerateMasterSecret ( cmd . OutOrStdout ( ) , fileHandler , flags . masterSecretPath )
2023-05-30 07:47:36 -04:00
i . masterSecret = masterSecret
2022-08-12 04:20:19 -04:00
if err != nil {
2022-10-21 06:01:28 -04:00
return fmt . Errorf ( "parsing or generating master secret from file %s: %w" , flags . masterSecretPath , err )
2022-08-12 04:20:19 -04:00
}
2022-11-24 10:39:33 -05:00
helmLoader := helm . NewLoader ( provider , k8sVersion )
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Created new Helm loader" )
2023-05-03 05:11:53 -04:00
helmDeployments , err := helmLoader . Load ( conf , flags . conformance , masterSecret . Key , masterSecret . Salt )
2023-02-10 07:27:22 -05:00
i . log . Debugf ( "Loaded Helm deployments" )
2022-08-31 07:57:59 -04:00
if err != nil {
2022-10-21 06:01:28 -04:00
return fmt . Errorf ( "loading Helm charts: %w" , err )
2022-08-31 07:57:59 -04:00
}
2023-02-10 07:27:22 -05:00
clusterName := conf . Name + "-" + idFile . UID
i . log . Debugf ( "Setting cluster name to %s" , clusterName )
2023-03-20 07:42:48 -04:00
cmd . PrintErrln ( "Note: If you just created the cluster, it can take a few minutes to connect." )
i . spinner . Start ( "Connecting " , false )
2022-06-21 11:59:12 -04:00
req := & initproto . InitRequest {
2023-01-16 05:19:03 -05:00
KmsUri : masterSecret . EncodeToURI ( ) ,
2023-03-02 09:08:31 -05:00
StorageUri : uri . NoStoreURI ,
2022-08-23 11:49:55 -04:00
CloudServiceAccountUri : serviceAccURI ,
2023-01-04 11:03:40 -05:00
KubernetesVersion : versions . VersionConfigs [ k8sVersion ] . ClusterVersion ,
2022-11-23 04:29:36 -05:00
KubernetesComponents : versions . VersionConfigs [ k8sVersion ] . KubernetesComponents . ToInitProto ( ) ,
2022-08-12 04:20:19 -04:00
HelmDeployments : helmDeployments ,
2022-09-20 04:07:55 -04:00
ConformanceMode : flags . conformance ,
2022-11-26 13:44:34 -05:00
InitSecret : idFile . InitSecret ,
2023-02-10 07:27:22 -05:00
ClusterName : clusterName ,
2022-03-22 11:03:15 -04:00
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Sending initialization request" )
resp , err := i . initCall ( cmd . Context ( ) , newDialer ( validator ) , idFile . IP , req )
2023-03-20 07:42:48 -04:00
i . spinner . Stop ( )
2023-05-30 07:47:36 -04:00
2022-03-22 11:03:15 -04:00
if err != nil {
2022-11-25 04:02:12 -05:00
var nonRetriable * nonRetriableError
if errors . As ( err , & nonRetriable ) {
cmd . PrintErrln ( "Cluster initialization failed. This error is not recoverable." )
cmd . PrintErrln ( "Terminate your cluster and try again." )
2023-05-30 07:47:36 -04:00
cmd . PrintErrf ( "The cluster logs were saved to %q\n" , constants . ErrorLog )
2022-11-25 04:02:12 -05:00
}
2022-03-22 11:03:15 -04:00
return err
}
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Initialization request succeeded" )
i . log . Debugf ( "Writing Constellation ID file" )
2022-10-11 06:24:33 -04:00
idFile . CloudProvider = provider
2022-07-29 04:01:10 -04:00
2023-03-20 06:06:51 -04:00
return i . writeOutput ( idFile , resp , flags . mergeConfigs , cmd . OutOrStdout ( ) , fileHandler )
2022-03-22 11:03:15 -04:00
}
2023-05-30 07:47:36 -04:00
func ( i * initCmd ) initCall ( ctx context . Context , dialer grpcDialer , ip string , req * initproto . InitRequest ) ( * initproto . InitSuccessResponse , error ) {
2022-06-21 11:59:12 -04:00
doer := & initDoer {
dialer : dialer ,
2022-06-29 09:26:29 -04:00
endpoint : net . JoinHostPort ( ip , strconv . Itoa ( constants . BootstrapperPort ) ) ,
2022-06-21 11:59:12 -04:00
req : req ,
2023-01-04 04:46:29 -05:00
log : i . log ,
2023-03-20 07:42:48 -04:00
spinner : i . spinner ,
2023-05-30 07:47:36 -04:00
fh : file . NewHandler ( afero . NewOsFs ( ) ) ,
2022-03-22 11:03:15 -04:00
}
2023-01-18 07:10:24 -05:00
// Create a wrapper function that allows logging any returned error from the retrier before checking if it's the expected retriable one.
serviceIsUnavailable := func ( err error ) bool {
isServiceUnavailable := grpcRetry . ServiceIsUnavailable ( err )
i . log . Debugf ( "Encountered error (retriable: %t): %s" , isServiceUnavailable , err )
return isServiceUnavailable
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Making initialization call, doer is %+v" , doer )
2023-01-18 07:10:24 -05:00
retrier := retry . NewIntervalRetrier ( doer , 30 * time . Second , serviceIsUnavailable )
2022-06-29 08:28:37 -04:00
if err := retrier . Do ( ctx ) ; err != nil {
2022-06-21 11:59:12 -04:00
return nil , err
2022-03-22 11:03:15 -04:00
}
2022-06-21 11:59:12 -04:00
return doer . resp , nil
2022-03-22 11:03:15 -04:00
}
2022-06-21 11:59:12 -04:00
type initDoer struct {
2023-03-20 07:44:12 -04:00
dialer grpcDialer
endpoint string
req * initproto . InitRequest
2023-05-30 07:47:36 -04:00
resp * initproto . InitSuccessResponse
2023-03-20 07:44:12 -04:00
log debugLog
spinner spinnerInterf
connectedOnce bool
2023-05-30 07:47:36 -04:00
fh file . Handler
2022-03-22 11:03:15 -04:00
}
2022-06-21 11:59:12 -04:00
func ( d * initDoer ) Do ( ctx context . Context ) error {
2023-03-20 07:44:12 -04:00
// connectedOnce is set in handleGRPCStateChanges when a connection was established in one retry attempt.
// This should cancel any other retry attempts when the connection is lost since the bootstrapper likely won't accept any new attempts anymore.
if d . connectedOnce {
2023-03-20 10:14:36 -04:00
return & nonRetriableError { errors . New ( "init already connected to the remote server in a previous attempt - resumption is not supported" ) }
2023-03-20 07:44:12 -04:00
}
2022-06-21 11:59:12 -04:00
conn , err := d . dialer . Dial ( ctx , d . endpoint )
if err != nil {
2023-05-30 07:47:36 -04:00
d . log . Debugf ( "Dialing init server failed: %s. Retrying..." , err )
2022-06-21 11:59:12 -04:00
return fmt . Errorf ( "dialing init server: %w" , err )
}
2022-07-05 08:14:11 -04:00
defer conn . Close ( )
2023-03-03 03:38:57 -05:00
var wg sync . WaitGroup
defer wg . Wait ( )
grpcStateLogCtx , grpcStateLogCancel := context . WithCancel ( ctx )
defer grpcStateLogCancel ( )
2023-03-20 07:42:48 -04:00
d . handleGRPCStateChanges ( grpcStateLogCtx , & wg , conn )
2023-03-03 03:38:57 -05:00
2022-06-21 11:59:12 -04:00
protoClient := initproto . NewAPIClient ( conn )
2023-01-04 04:46:29 -05:00
d . log . Debugf ( "Created protoClient" )
2022-06-21 11:59:12 -04:00
resp , err := protoClient . Init ( ctx , d . req )
2022-03-29 05:38:14 -04:00
if err != nil {
2022-11-25 04:02:12 -05:00
return & nonRetriableError { fmt . Errorf ( "init call: %w" , err ) }
2022-03-29 05:38:14 -04:00
}
2023-05-30 07:47:36 -04:00
res , err := resp . Recv ( ) // get first response, either success or failure
if err != nil {
if e := d . getLogs ( resp ) ; e != nil {
d . log . Debugf ( "Failed to collect logs: %s" , e )
}
return & nonRetriableError { err }
}
switch res . Kind . ( type ) {
case * initproto . InitResponse_InitFailure :
if e := d . getLogs ( resp ) ; e != nil {
d . log . Debugf ( "Failed to get logs from cluster: %s" , e )
}
return & nonRetriableError { errors . New ( res . GetInitFailure ( ) . GetError ( ) ) }
case * initproto . InitResponse_InitSuccess :
d . resp = res . GetInitSuccess ( )
case nil :
d . log . Debugf ( "Cluster returned nil response type" )
return & nonRetriableError { errors . New ( "empty response from cluster" ) }
default :
d . log . Debugf ( "Cluster returned unknown response type" )
return & nonRetriableError { errors . New ( "unknown response from cluster" ) }
}
return nil
}
func ( d * initDoer ) getLogs ( resp initproto . API_InitClient ) error {
d . log . Debugf ( "Attempting to collect cluster logs" )
for {
res , err := resp . Recv ( )
if err == io . EOF {
break
}
if err != nil {
return err
}
log := res . GetLog ( ) . GetLog ( )
if log == nil {
return errors . New ( "sent empty logs" )
}
if err := d . fh . Write ( constants . ErrorLog , log , file . OptAppend ) ; err != nil {
return err
}
}
2022-06-21 11:59:12 -04:00
return nil
2022-03-29 05:38:14 -04:00
}
2023-03-20 07:42:48 -04:00
func ( d * initDoer ) handleGRPCStateChanges ( ctx context . Context , wg * sync . WaitGroup , conn * grpc . ClientConn ) {
2023-06-12 07:45:34 -04:00
grpclog . LogStateChangesUntilReady ( ctx , conn , d . log , wg , func ( ) {
d . connectedOnce = true
d . spinner . Stop ( )
d . spinner . Start ( "Initializing cluster " , false )
} )
2023-03-03 03:38:57 -05:00
}
2023-02-10 08:59:44 -05:00
func ( i * initCmd ) writeOutput (
2023-05-30 07:47:36 -04:00
idFile clusterid . File , initResp * initproto . InitSuccessResponse , mergeConfig bool , wr io . Writer , fileHandler file . Handler ,
2023-02-10 08:59:44 -05:00
) error {
2022-04-27 08:21:36 -04:00
fmt . Fprint ( wr , "Your Constellation cluster was successfully initialized.\n\n" )
2022-04-05 03:13:09 -04:00
2023-05-30 07:47:36 -04:00
ownerID := hex . EncodeToString ( initResp . GetOwnerId ( ) )
2023-01-18 07:10:24 -05:00
// i.log.Debugf("Owner id is %s", ownerID)
2023-05-30 07:47:36 -04:00
clusterID := hex . EncodeToString ( initResp . GetClusterId ( ) )
2022-07-05 08:14:11 -04:00
2022-04-05 03:13:09 -04:00
tw := tabwriter . NewWriter ( wr , 0 , 0 , 2 , ' ' , 0 )
2022-07-26 04:58:39 -04:00
// writeRow(tw, "Constellation cluster's owner identifier", ownerID)
2022-07-27 10:10:50 -04:00
writeRow ( tw , "Constellation cluster identifier" , clusterID )
2022-04-06 04:36:58 -04:00
writeRow ( tw , "Kubernetes configuration" , constants . AdminConfFilename )
2022-04-05 03:13:09 -04:00
tw . Flush ( )
fmt . Fprintln ( wr )
2023-05-30 07:47:36 -04:00
if err := fileHandler . Write ( constants . AdminConfFilename , initResp . GetKubeconfig ( ) , file . OptNone ) ; err != nil {
2022-07-29 03:52:47 -04:00
return fmt . Errorf ( "writing kubeconfig: %w" , err )
2022-03-22 11:03:15 -04:00
}
2023-03-03 10:50:25 -05:00
i . log . Debugf ( "Kubeconfig written to %s" , constants . AdminConfFilename )
2023-02-10 08:59:44 -05:00
if mergeConfig {
if err := i . merger . mergeConfigs ( constants . AdminConfFilename , fileHandler ) ; err != nil {
2023-03-08 09:48:36 -05:00
writeRow ( tw , "Failed to automatically merge kubeconfig" , err . Error ( ) )
mergeConfig = false // Set to false so we don't print the wrong message below.
} else {
writeRow ( tw , "Kubernetes configuration merged with default config" , "" )
2023-02-10 08:59:44 -05:00
}
}
2022-10-11 06:24:33 -04:00
idFile . OwnerID = ownerID
idFile . ClusterID = clusterID
2022-07-29 04:01:10 -04:00
if err := fileHandler . WriteJSON ( constants . ClusterIDsFileName , idFile , file . OptOverwrite ) ; err != nil {
2023-02-10 08:59:44 -05:00
return fmt . Errorf ( "writing Constellation ID file: %w" , err )
2022-07-01 04:57:29 -04:00
}
2023-03-03 10:50:25 -05:00
i . log . Debugf ( "Constellation ID file written to %s" , constants . ClusterIDsFileName )
2023-02-10 08:59:44 -05:00
if ! mergeConfig {
fmt . Fprintln ( wr , "You can now connect to your cluster by executing:" )
fmt . Fprintf ( wr , "\texport KUBECONFIG=\"$PWD/%s\"\n" , constants . AdminConfFilename )
} else {
fmt . Fprintln ( wr , "Constellation kubeconfig merged with default config." )
2022-07-01 04:57:29 -04:00
2023-02-10 08:59:44 -05:00
if i . merger . kubeconfigEnvVar ( ) != "" {
fmt . Fprintln ( wr , "Warning: KUBECONFIG environment variable is set." )
fmt . Fprintln ( wr , "You may need to unset it to use the default config and connect to your cluster." )
} else {
fmt . Fprintln ( wr , "You can now connect to your cluster." )
}
}
2022-03-22 11:03:15 -04:00
return nil
}
2022-04-05 03:13:09 -04:00
func writeRow ( wr io . Writer , col1 string , col2 string ) {
fmt . Fprint ( wr , col1 , "\t" , col2 , "\n" )
}
2022-03-22 11:03:15 -04:00
// evalFlagArgs gets the flag values and does preprocessing of these values like
// reading the content from file path flags and deriving other values from flag combinations.
2023-01-04 04:46:29 -05:00
func ( i * initCmd ) evalFlagArgs ( cmd * cobra . Command ) ( initFlags , error ) {
2022-03-22 11:03:15 -04:00
masterSecretPath , err := cmd . Flags ( ) . GetString ( "master-secret" )
if err != nil {
2022-07-29 04:01:10 -04:00
return initFlags { } , fmt . Errorf ( "parsing master-secret path flag: %w" , err )
2022-03-22 11:03:15 -04:00
}
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Master secret path flag value is %q" , masterSecretPath )
2022-09-20 04:07:55 -04:00
conformance , err := cmd . Flags ( ) . GetBool ( "conformance" )
if err != nil {
2023-01-18 07:10:24 -05:00
return initFlags { } , fmt . Errorf ( "parsing conformance flag: %w" , err )
2022-09-20 04:07:55 -04:00
}
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Conformance flag is %t" , conformance )
2022-05-13 05:56:43 -04:00
configPath , err := cmd . Flags ( ) . GetString ( "config" )
2022-04-13 09:01:02 -04:00
if err != nil {
2022-07-29 04:01:10 -04:00
return initFlags { } , fmt . Errorf ( "parsing config path flag: %w" , err )
2022-03-22 11:03:15 -04:00
}
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Configuration path flag is %q" , configPath )
2023-02-10 08:59:44 -05:00
mergeConfigs , err := cmd . Flags ( ) . GetBool ( "merge-kubeconfig" )
if err != nil {
return initFlags { } , fmt . Errorf ( "parsing merge-kubeconfig flag: %w" , err )
}
i . log . Debugf ( "Merge kubeconfig flag is %t" , mergeConfigs )
2022-03-22 11:03:15 -04:00
2023-01-31 05:45:31 -05:00
force , err := cmd . Flags ( ) . GetBool ( "force" )
if err != nil {
return initFlags { } , fmt . Errorf ( "parsing force argument: %w" , err )
}
2023-02-22 12:29:25 -05:00
i . log . Debugf ( "force flag is %t" , force )
2023-01-31 05:45:31 -05:00
2022-04-13 09:01:02 -04:00
return initFlags {
2022-08-31 07:57:59 -04:00
configPath : configPath ,
2022-09-20 04:07:55 -04:00
conformance : conformance ,
2022-08-31 07:57:59 -04:00
masterSecretPath : masterSecretPath ,
2023-01-31 05:45:31 -05:00
force : force ,
2023-02-10 08:59:44 -05:00
mergeConfigs : mergeConfigs ,
2022-03-22 11:03:15 -04:00
} , nil
}
2022-04-13 09:01:02 -04:00
// initFlags are the resulting values of flag preprocessing.
type initFlags struct {
2022-08-31 07:57:59 -04:00
configPath string
masterSecretPath string
2022-09-20 04:07:55 -04:00
conformance bool
2023-01-31 05:45:31 -05:00
force bool
2023-02-10 08:59:44 -05:00
mergeConfigs bool
2022-03-22 11:03:15 -04:00
}
2022-06-09 10:10:42 -04:00
// readOrGenerateMasterSecret reads a base64 encoded master secret from file or generates a new 32 byte secret.
2023-03-02 09:08:31 -05:00
func ( i * initCmd ) readOrGenerateMasterSecret ( outWriter io . Writer , fileHandler file . Handler , filename string ) ( uri . MasterSecret , error ) {
2022-03-22 11:03:15 -04:00
if filename != "" {
2023-01-18 07:10:24 -05:00
i . log . Debugf ( "Reading master secret from file %q" , filename )
2023-03-02 09:08:31 -05:00
var secret uri . MasterSecret
2022-07-29 03:52:47 -04:00
if err := fileHandler . ReadJSON ( filename , & secret ) ; err != nil {
2023-03-02 09:08:31 -05:00
return uri . MasterSecret { } , err
2022-03-22 11:03:15 -04:00
}
2022-07-29 03:52:47 -04:00
if len ( secret . Key ) < crypto . MasterSecretLengthMin {
2023-03-02 09:08:31 -05:00
return uri . MasterSecret { } , fmt . Errorf ( "provided master secret is smaller than the required minimum of %d Bytes" , crypto . MasterSecretLengthMin )
2022-03-22 11:03:15 -04:00
}
2022-07-29 03:52:47 -04:00
if len ( secret . Salt ) < crypto . RNGLengthDefault {
2023-03-02 09:08:31 -05:00
return uri . MasterSecret { } , fmt . Errorf ( "provided salt is smaller than the required minimum of %d Bytes" , crypto . RNGLengthDefault )
2022-03-22 11:03:15 -04:00
}
2022-07-29 03:52:47 -04:00
return secret , nil
2022-03-22 11:03:15 -04:00
}
// No file given, generate a new secret, and save it to disk
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Generating new master secret" )
2022-07-29 03:52:47 -04:00
key , err := crypto . GenerateRandomBytes ( crypto . MasterSecretLengthDefault )
2022-03-22 11:03:15 -04:00
if err != nil {
2023-03-02 09:08:31 -05:00
return uri . MasterSecret { } , err
2022-03-22 11:03:15 -04:00
}
2022-07-29 03:52:47 -04:00
salt , err := crypto . GenerateRandomBytes ( crypto . RNGLengthDefault )
if err != nil {
2023-03-02 09:08:31 -05:00
return uri . MasterSecret { } , err
2022-07-29 03:52:47 -04:00
}
2023-03-02 09:08:31 -05:00
secret := uri . MasterSecret {
2022-07-29 03:52:47 -04:00
Key : key ,
Salt : salt ,
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Generated master secret key and salt values" )
2022-07-29 03:52:47 -04:00
if err := fileHandler . WriteJSON ( constants . MasterSecretFilename , secret , file . OptNone ) ; err != nil {
2023-03-02 09:08:31 -05:00
return uri . MasterSecret { } , err
2022-03-22 11:03:15 -04:00
}
2022-11-10 04:27:24 -05:00
fmt . Fprintf ( outWriter , "Your Constellation master secret was successfully written to ./%s\n" , constants . MasterSecretFilename )
2022-07-29 03:52:47 -04:00
return secret , nil
2022-03-22 11:03:15 -04:00
}
2023-01-04 04:46:29 -05:00
func ( i * initCmd ) getMarshaledServiceAccountURI ( provider cloudprovider . Provider , config * config . Config , fileHandler file . Handler ) ( string , error ) {
i . log . Debugf ( "Getting service account URI" )
2022-08-23 11:49:55 -04:00
switch provider {
case cloudprovider . GCP :
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Handling case for GCP" )
2022-08-23 11:49:55 -04:00
path := config . Provider . GCP . ServiceAccountKeyPath
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "GCP service account key path %s" , path )
2022-08-23 11:49:55 -04:00
var key gcpshared . ServiceAccountKey
if err := fileHandler . ReadJSON ( path , & key ) ; err != nil {
return "" , fmt . Errorf ( "reading service account key from path %q: %w" , path , err )
}
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Read GCP service account key from path" )
2022-08-23 11:49:55 -04:00
return key . ToCloudServiceAccountURI ( ) , nil
2022-10-21 11:09:26 -04:00
case cloudprovider . AWS :
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Handling case for AWS" )
2022-10-21 11:09:26 -04:00
return "" , nil // AWS does not need a service account URI
2022-08-23 11:49:55 -04:00
case cloudprovider . Azure :
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Handling case for Azure" )
2023-05-26 05:45:03 -04:00
authMethod := azureshared . AuthMethodUserAssignedIdentity
2022-08-29 08:18:05 -04:00
creds := azureshared . ApplicationCredentials {
2023-05-26 05:45:03 -04:00
TenantID : config . Provider . Azure . TenantID ,
Location : config . Provider . Azure . Location ,
PreferredAuthMethod : authMethod ,
2023-04-03 09:01:25 -04:00
UamiResourceID : config . Provider . Azure . UserAssignedIdentity ,
2022-08-29 08:18:05 -04:00
}
return creds . ToCloudServiceAccountURI ( ) , nil
2022-08-23 11:49:55 -04:00
2023-03-17 04:43:22 -04:00
case cloudprovider . OpenStack :
creds := openstack . AccountKey {
AuthURL : config . Provider . OpenStack . AuthURL ,
Username : config . Provider . OpenStack . Username ,
Password : config . Provider . OpenStack . Password ,
ProjectID : config . Provider . OpenStack . ProjectID ,
ProjectName : config . Provider . OpenStack . ProjectName ,
UserDomainName : config . Provider . OpenStack . UserDomainName ,
ProjectDomainName : config . Provider . OpenStack . ProjectDomainName ,
RegionName : config . Provider . OpenStack . RegionName ,
}
return creds . ToCloudServiceAccountURI ( ) , nil
2022-08-23 11:49:55 -04:00
case cloudprovider . QEMU :
2023-01-04 04:46:29 -05:00
i . log . Debugf ( "Handling case for QEMU" )
2022-08-23 11:49:55 -04:00
return "" , nil // QEMU does not use service account keys
default :
return "" , fmt . Errorf ( "unsupported cloud provider %q" , provider )
}
}
2023-02-10 08:59:44 -05:00
type configMerger interface {
mergeConfigs ( configPath string , fileHandler file . Handler ) error
kubeconfigEnvVar ( ) string
}
type kubeconfigMerger struct {
log debugLog
}
func ( c * kubeconfigMerger ) mergeConfigs ( configPath string , fileHandler file . Handler ) error {
constellConfig , err := clientcmd . LoadFromFile ( configPath )
if err != nil {
return fmt . Errorf ( "loading admin kubeconfig: %w" , err )
}
loadingRules := clientcmd . NewDefaultClientConfigLoadingRules ( )
loadingRules . Precedence = [ ] string {
2023-03-08 09:48:36 -05:00
clientcmd . RecommendedHomeFile ,
configPath , // our config should overwrite the default config
2023-02-10 08:59:44 -05:00
}
c . log . Debugf ( "Kubeconfig file loading precedence: %v" , loadingRules . Precedence )
2023-03-08 09:48:36 -05:00
// merge the kubeconfigs
2023-02-10 08:59:44 -05:00
cfg , err := loadingRules . Load ( )
if err != nil {
return fmt . Errorf ( "loading merged kubeconfig: %w" , err )
}
// Set the current context to the cluster we just created
cfg . CurrentContext = constellConfig . CurrentContext
c . log . Debugf ( "Set current context to %s" , cfg . CurrentContext )
json , err := runtime . Encode ( clientcodec . Codec , cfg )
if err != nil {
return fmt . Errorf ( "encoding merged kubeconfig: %w" , err )
}
mergedKubeconfig , err := yaml . JSONToYAML ( json )
if err != nil {
return fmt . Errorf ( "converting merged kubeconfig to YAML: %w" , err )
}
if err := fileHandler . Write ( clientcmd . RecommendedHomeFile , mergedKubeconfig , file . OptOverwrite ) ; err != nil {
return fmt . Errorf ( "writing merged kubeconfig to file: %w" , err )
}
c . log . Debugf ( "Merged kubeconfig into default config file: %s" , clientcmd . RecommendedHomeFile )
return nil
}
func ( c * kubeconfigMerger ) kubeconfigEnvVar ( ) string {
return os . Getenv ( clientcmd . RecommendedConfigPathEnvVar )
}
2022-06-21 11:59:12 -04:00
type grpcDialer interface {
Dial ( ctx context . Context , target string ) ( * grpc . ClientConn , error )
}
2022-11-25 04:02:12 -05:00
type nonRetriableError struct {
err error
}
// Error returns the error message.
func ( e * nonRetriableError ) Error ( ) string {
return e . err . Error ( )
}
// Unwrap returns the wrapped error.
func ( e * nonRetriableError ) Unwrap ( ) error {
return e . err
}