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 (
2023-07-31 04:53:05 -04:00
"bytes"
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"
2023-08-04 07:53:51 -04:00
"path/filepath"
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
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"
2023-08-08 09:42:06 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
2023-08-23 02:14:39 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/kubecmd"
2023-08-03 07:54:48 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"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-09-20 04:07:55 -04:00
cmd . Flags ( ) . Bool ( "conformance" , false , "enable conformance mode" )
2023-07-07 11:09:45 -04:00
cmd . Flags ( ) . Bool ( "skip-helm-wait" , false , "install helm charts without waiting for deployments to be ready" )
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-07-31 04:53:05 -04:00
log debugLog
merger configMerger
spinner spinnerInterf
2023-08-04 04:42:09 -04:00
fileHandler file . Handler
clusterShower clusterShower
2023-08-08 09:42:06 -04:00
pf pathprefix . PathPrefixer
2023-08-03 07:54:48 -04:00
}
2023-08-04 04:42:09 -04:00
func newInitCmd (
2023-08-24 10:40:47 -04:00
clusterShower clusterShower , fileHandler file . Handler ,
2023-08-04 04:42:09 -04:00
spinner spinnerInterf , merger configMerger , log debugLog ,
) * initCmd {
return & initCmd {
log : log ,
merger : merger ,
spinner : spinner ,
fileHandler : fileHandler ,
clusterShower : clusterShower ,
}
}
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-08-04 07:53:51 -04:00
2023-08-03 07:54:48 -04:00
tfClient , err := terraform . New ( ctx , constants . TerraformWorkingDir )
if err != nil {
return fmt . Errorf ( "creating Terraform client: %w" , err )
}
2023-08-23 02:14:39 -04:00
2023-08-24 10:40:47 -04:00
i := newInitCmd ( tfClient , fileHandler , spinner , & kubeconfigMerger { log : log } , log )
2023-06-07 10:16:32 -04:00
fetcher := attestationconfigapi . NewFetcher ( )
2023-08-23 02:14:39 -04:00
newAttestationApplier := func ( w io . Writer , kubeConfig string , log debugLog ) ( attestationConfigApplier , error ) {
2023-08-24 10:40:47 -04:00
return kubecmd . New ( w , kubeConfig , fileHandler , log )
2023-08-23 02:14:39 -04:00
}
2023-08-24 10:40:47 -04:00
newHelmClient := func ( kubeConfigPath string , log debugLog ) ( helmApplier , error ) {
return helm . NewClient ( kubeConfigPath , log )
} // need to defer helm client instantiation until kubeconfig is available
2023-08-23 02:14:39 -04:00
2023-08-24 10:40:47 -04:00
return i . initialize ( cmd , newDialer , license . NewClient ( ) , fetcher , newAttestationApplier , newHelmClient )
2022-03-22 11:03:15 -04:00
}
2022-06-29 09:26:29 -04:00
// initialize initializes a Constellation.
2023-08-23 02:14:39 -04:00
func ( i * initCmd ) initialize (
cmd * cobra . Command , newDialer func ( validator atls . Validator ) * dialer . Dialer ,
2023-08-04 04:42:09 -04:00
quotaChecker license . QuotaChecker , configFetcher attestationconfigapi . Fetcher ,
2023-08-23 02:14:39 -04:00
newAttestationApplier func ( io . Writer , string , debugLog ) ( attestationConfigApplier , error ) ,
2023-08-24 10:40:47 -04:00
newHelmClient func ( kubeConfigPath string , log debugLog ) ( helmApplier , error ) ,
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-08-16 09:38:40 -04:00
i . log . Debugf ( "Loading configuration file from %q" , i . pf . PrefixPrintablePath ( constants . ConfigFilename ) )
2023-08-04 07:53:51 -04:00
conf , err := config . New ( i . fileHandler , constants . ConfigFilename , 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-09-19 07:50:00 -04:00
// cfg validation does not check k8s patch version since upgrade may accept an outdated patch version.
k8sVersion , err := versions . NewValidK8sVersion ( string ( conf . KubernetesVersion ) , true )
if err != nil {
return err
}
2023-06-27 12:24:35 -04:00
if ! flags . force {
2023-07-25 08:20:25 -04:00
if err := validateCLIandConstellationVersionAreEqual ( constants . BinaryVersion ( ) , conf . Image , conf . MicroserviceVersion ) ; err != nil {
2023-06-27 12:24:35 -04:00
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
2023-08-04 07:53:51 -04:00
if err := i . fileHandler . ReadJSON ( constants . ClusterIDsFilename , & idFile ) ; err != nil {
2022-10-11 06:24:33 -04:00
return fmt . Errorf ( "reading cluster ID file: %w" , err )
}
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 ( ) )
2023-08-04 04:42:09 -04:00
checker := license . NewChecker ( quotaChecker , i . 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" )
2023-08-11 09:18:59 -04:00
serviceAccURI , err := cloudcmd . GetMarshaledServiceAccountURI ( provider , conf , i . pf , i . log , i . 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-08-07 09:24:46 -04:00
i . log . Debugf ( "Generating master secret" )
2023-08-08 09:42:06 -04:00
masterSecret , err := i . generateMasterSecret ( cmd . OutOrStdout ( ) )
2022-08-12 04:20:19 -04:00
if err != nil {
2023-08-04 07:53:51 -04:00
return fmt . Errorf ( "generating master secret: %w" , err )
2022-08-12 04:20:19 -04:00
}
2023-08-07 09:24:46 -04:00
i . log . Debugf ( "Generated measurement salt" )
measurementSalt , err := crypto . GenerateRandomBytes ( crypto . RNGLengthDefault )
if err != nil {
return fmt . Errorf ( "generating measurement salt: %w" , err )
}
2023-08-11 09:18:59 -04:00
idFile . MeasurementSalt = measurementSalt
2023-07-24 04:30:53 -04:00
clusterName := clusterid . GetClusterName ( conf , idFile )
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-08-09 08:16:45 -04:00
KmsUri : masterSecret . EncodeToURI ( ) ,
StorageUri : uri . NoStoreURI ,
MeasurementSalt : measurementSalt ,
KubernetesVersion : versions . VersionConfigs [ k8sVersion ] . ClusterVersion ,
KubernetesComponents : versions . VersionConfigs [ k8sVersion ] . KubernetesComponents . ToInitProto ( ) ,
ConformanceMode : flags . conformance ,
InitSecret : idFile . InitSecret ,
ClusterName : clusterName ,
ApiserverCertSans : idFile . APIServerCertSANs ,
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-09-25 06:10:07 -04:00
if nonRetriable . logCollectionErr != nil {
cmd . PrintErrf ( "Failed to collect logs from bootstrapper: %s\n" , nonRetriable . logCollectionErr )
} else {
cmd . PrintErrf ( "Fetched bootstrapper logs are stored in %q\n" , i . pf . PrefixPrintablePath ( 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" )
2023-07-31 04:53:05 -04:00
2023-01-18 07:10:24 -05:00
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-07-31 04:53:05 -04:00
bufferedOutput := & bytes . Buffer { }
2023-08-08 09:42:06 -04:00
if err := i . writeOutput ( idFile , resp , flags . mergeConfigs , bufferedOutput ) ; err != nil {
2023-07-31 04:53:05 -04:00
return err
}
2023-08-03 07:54:48 -04:00
2023-08-23 02:14:39 -04:00
attestationApplier , err := newAttestationApplier ( cmd . OutOrStdout ( ) , constants . AdminConfFilename , i . log )
if err != nil {
return err
}
if err := attestationApplier . ApplyJoinConfig ( cmd . Context ( ) , conf . GetAttestationConfig ( ) , measurementSalt ) ; err != nil {
return fmt . Errorf ( "applying attestation config: %w" , err )
}
2023-08-04 04:42:09 -04:00
output , err := i . clusterShower . ShowCluster ( cmd . Context ( ) , conf . GetProvider ( ) )
2023-08-03 07:54:48 -04:00
if err != nil {
return fmt . Errorf ( "getting Terraform output: %w" , err )
}
2023-08-21 09:12:23 -04:00
i . spinner . Start ( "Installing Kubernetes components " , false )
2023-08-24 10:40:47 -04:00
options := helm . Options {
Force : flags . force ,
Conformance : flags . conformance ,
HelmWaitMode : flags . helmWaitMode ,
AllowDestructive : helm . DenyDestructive ,
}
helmApplier , err := newHelmClient ( constants . AdminConfFilename , i . log )
2023-08-03 07:54:48 -04:00
if err != nil {
2023-08-24 10:40:47 -04:00
return fmt . Errorf ( "creating Helm client: %w" , err )
2023-08-03 07:54:48 -04:00
}
2023-09-19 07:50:00 -04:00
executor , includesUpgrades , err := helmApplier . PrepareApply ( conf , idFile , options , output ,
2023-08-24 10:40:47 -04:00
serviceAccURI , masterSecret )
if err != nil {
return fmt . Errorf ( "getting Helm chart executor: %w" , err )
}
if includesUpgrades {
return errors . New ( "init: helm tried to upgrade charts instead of installing them" )
}
if err := executor . Apply ( cmd . Context ( ) ) ; err != nil {
return fmt . Errorf ( "applying Helm charts: %w" , err )
2023-07-31 04:53:05 -04:00
}
2023-08-21 09:12:23 -04:00
i . spinner . Stop ( )
i . log . Debugf ( "Helm deployment installation succeeded" )
2023-07-31 04:53:05 -04:00
cmd . Println ( bufferedOutput . String ( ) )
return nil
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-09-25 06:10:07 -04:00
return & nonRetriableError {
logCollectionErr : errors . New ( "init already connected to the remote server in a previous attempt - resumption is not supported" ) ,
err : 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 {
2023-09-25 06:10:07 -04:00
return & nonRetriableError {
logCollectionErr : errors . New ( "rpc failed before first response was received - no logs available" ) ,
err : 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 )
2023-09-25 06:10:07 -04:00
return & nonRetriableError {
logCollectionErr : e ,
err : err ,
}
2023-05-30 07:47:36 -04:00
}
2023-09-25 06:10:07 -04:00
return & nonRetriableError { err : err }
2023-05-30 07:47:36 -04:00
}
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 )
2023-09-25 06:10:07 -04:00
return & nonRetriableError {
logCollectionErr : e ,
err : errors . New ( res . GetInitFailure ( ) . GetError ( ) ) ,
}
2023-05-30 07:47:36 -04:00
}
2023-09-25 06:10:07 -04:00
return & nonRetriableError { err : errors . New ( res . GetInitFailure ( ) . GetError ( ) ) }
2023-05-30 07:47:36 -04:00
case * initproto . InitResponse_InitSuccess :
d . resp = res . GetInitSuccess ( )
case nil :
d . log . Debugf ( "Cluster returned nil response type" )
2023-09-25 06:10:07 -04:00
err = errors . New ( "empty response from cluster" )
if e := d . getLogs ( resp ) ; e != nil {
d . log . Debugf ( "Failed to collect logs: %s" , e )
return & nonRetriableError {
logCollectionErr : e ,
err : err ,
}
}
return & nonRetriableError { err : err }
2023-05-30 07:47:36 -04:00
default :
d . log . Debugf ( "Cluster returned unknown response type" )
2023-09-25 06:10:07 -04:00
err = errors . New ( "unknown response from cluster" )
if e := d . getLogs ( resp ) ; e != nil {
d . log . Debugf ( "Failed to collect logs: %s" , e )
return & nonRetriableError {
logCollectionErr : e ,
err : err ,
}
}
return & nonRetriableError { err : err }
2023-05-30 07:47:36 -04:00
}
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
}
2023-09-25 06:10:07 -04:00
switch res . Kind . ( type ) {
case * initproto . InitResponse_InitFailure :
return errors . New ( "trying to collect logs: received init failure response, expected log response" )
case * initproto . InitResponse_InitSuccess :
return errors . New ( "trying to collect logs: received init success response, expected log response" )
case nil :
return errors . New ( "trying to collect logs: received nil response, expected log response" )
}
2023-05-30 07:47:36 -04:00
log := res . GetLog ( ) . GetLog ( )
if log == nil {
2023-09-25 06:10:07 -04:00
return errors . New ( "received empty logs" )
2023-05-30 07:47:36 -04:00
}
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-08-08 09:42:06 -04:00
idFile clusterid . File , initResp * initproto . InitSuccessResponse , mergeConfig bool , wr io . Writer ,
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 )
2023-08-16 09:38:40 -04:00
writeRow ( tw , "Kubernetes configuration" , i . pf . PrefixPrintablePath ( constants . AdminConfFilename ) )
2022-04-05 03:13:09 -04:00
tw . Flush ( )
fmt . Fprintln ( wr )
2023-08-04 04:42:09 -04:00
if err := i . 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-08-16 09:38:40 -04:00
i . log . Debugf ( "Kubeconfig written to %s" , i . pf . PrefixPrintablePath ( constants . AdminConfFilename ) )
2023-02-10 08:59:44 -05:00
if mergeConfig {
2023-08-04 04:42:09 -04:00
if err := i . merger . mergeConfigs ( constants . AdminConfFilename , i . 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
2023-08-04 07:53:51 -04:00
if err := i . 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-08-16 09:38:40 -04:00
i . log . Debugf ( "Constellation ID file written to %s" , i . pf . PrefixPrintablePath ( constants . ClusterIDsFilename ) )
2023-02-10 08:59:44 -05:00
if ! mergeConfig {
fmt . Fprintln ( wr , "You can now connect to your cluster by executing:" )
2023-08-08 09:42:06 -04:00
2023-08-11 04:40:27 -04:00
exportPath , err := filepath . Abs ( constants . AdminConfFilename )
2023-08-08 09:42:06 -04:00
if err != nil {
return fmt . Errorf ( "getting absolute path to kubeconfig: %w" , err )
}
fmt . Fprintf ( wr , "\texport KUBECONFIG=%q\n" , exportPath )
2023-02-10 08:59:44 -05:00
} 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-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 )
2023-07-07 11:09:45 -04:00
skipHelmWait , err := cmd . Flags ( ) . GetBool ( "skip-helm-wait" )
if err != nil {
return initFlags { } , fmt . Errorf ( "parsing skip-helm-wait flag: %w" , err )
}
2023-08-02 09:49:40 -04:00
helmWaitMode := helm . WaitModeAtomic
2023-07-07 11:09:45 -04:00
if skipHelmWait {
2023-08-02 09:49:40 -04:00
helmWaitMode = helm . WaitModeNone
2023-07-07 11:09:45 -04:00
}
i . log . Debugf ( "Helm wait flag is %t" , skipHelmWait )
2023-08-08 09:42:06 -04:00
workDir , err := cmd . Flags ( ) . GetString ( "workspace" )
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-08-08 09:42:06 -04:00
i . pf = pathprefix . New ( workDir )
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 {
2023-08-04 07:53:51 -04:00
conformance : conformance ,
helmWaitMode : helmWaitMode ,
force : force ,
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 {
2023-08-04 07:53:51 -04:00
conformance bool
helmWaitMode helm . WaitMode
force bool
mergeConfigs bool
2022-03-22 11:03:15 -04:00
}
2023-08-11 09:18:59 -04:00
// generateMasterSecret reads a base64 encoded master secret from file or generates a new 32 byte secret.
2023-08-08 09:42:06 -04:00
func ( i * initCmd ) generateMasterSecret ( outWriter io . Writer ) ( uri . MasterSecret , error ) {
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" )
2023-08-04 04:42:09 -04:00
if err := i . 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
}
2023-08-16 09:38:40 -04:00
fmt . Fprintf ( outWriter , "Your Constellation master secret was successfully written to %q\n" , i . pf . PrefixPrintablePath ( constants . MasterSecretFilename ) )
2022-07-29 03:52:47 -04:00
return secret , nil
2022-03-22 11:03:15 -04:00
}
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 {
2023-09-25 06:10:07 -04:00
logCollectionErr error
err error
2022-11-25 04:02:12 -05:00
}
// 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
}
2023-08-04 07:53:51 -04:00
2023-08-23 02:14:39 -04:00
type attestationConfigApplier interface {
ApplyJoinConfig ( ctx context . Context , newAttestConfig config . AttestationCfg , measurementSalt [ ] byte ) error
}
2023-08-24 10:40:47 -04:00
type helmApplier interface {
2023-09-19 07:50:00 -04:00
PrepareApply ( conf * config . Config , idFile clusterid . File , flags helm . Options , tfOutput terraform . ApplyOutput , serviceAccURI string , masterSecret uri . MasterSecret ) ( helm . Applier , bool , error )
2023-08-24 10:40:47 -04:00
}
type clusterShower interface {
ShowCluster ( ctx context . Context , provider cloudprovider . Provider ) ( terraform . ApplyOutput , error )
}