Skip to content

Commit

Permalink
constellation-lib: refactor init RPC to be shared (#2665)
Browse files Browse the repository at this point in the history
* constellation-lib: refactor init RPC

Signed-off-by: Moritz Sanft <[email protected]>

* constellation-lib: pass io.Writer for collecting logs

Signed-off-by: Moritz Sanft <[email protected]>

* constellation-lib: add init test

Signed-off-by: Moritz Sanft <[email protected]>

* constellation-lib: bin dialer to struct

Signed-off-by: Moritz Sanft <[email protected]>

* constellation-lib: set service CIDR on init

Signed-off-by: Moritz Sanft <[email protected]>

---------

Signed-off-by: Moritz Sanft <[email protected]>
  • Loading branch information
msanft authored Dec 4, 2023
1 parent db49093 commit 17aecaa
Show file tree
Hide file tree
Showing 12 changed files with 759 additions and 343 deletions.
18 changes: 9 additions & 9 deletions cli/internal/cloudcmd/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

// NewValidator creates a new Validator.
func NewValidator(cmd *cobra.Command, config config.AttestationCfg, log debugLog) (atls.Validator, error) {
return choose.Validator(config, warnLogger{cmd: cmd, log: log})
return choose.Validator(config, WarnLogger{Cmd: cmd, Log: log})
}

// UpdateInitMeasurements sets the owner and cluster measurement values.
Expand Down Expand Up @@ -102,21 +102,21 @@ func decodeMeasurement(encoded string) ([]byte, error) {
return decoded, nil
}

// warnLogger implements logging of warnings for validators.
type warnLogger struct {
cmd *cobra.Command
log debugLog
// WarnLogger implements logging of warnings for validators.
type WarnLogger struct {
Cmd *cobra.Command
Log debugLog
}

// Infof messages are reduced to debug messages, since we don't want
// the extra info when using the CLI without setting the debug flag.
func (wl warnLogger) Infof(fmtStr string, args ...any) {
wl.log.Debugf(fmtStr, args...)
func (wl WarnLogger) Infof(fmtStr string, args ...any) {
wl.Log.Debugf(fmtStr, args...)
}

// Warnf prints a formatted warning from the validator.
func (wl warnLogger) Warnf(fmtStr string, args ...any) {
wl.cmd.PrintErrf("Warning: %s\n", fmt.Sprintf(fmtStr, args...))
func (wl WarnLogger) Warnf(fmtStr string, args ...any) {
wl.Cmd.PrintErrf("Warning: %s\n", fmt.Sprintf(fmtStr, args...))
}

type debugLog interface {
Expand Down
3 changes: 2 additions & 1 deletion cli/internal/cmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ go_library(
"//internal/api/fetcher",
"//internal/api/versionsapi",
"//internal/atls",
"//internal/attestation/choose",
"//internal/attestation/measurements",
"//internal/attestation/snp",
"//internal/attestation/variant",
Expand All @@ -76,7 +77,6 @@ go_library(
"//internal/featureset",
"//internal/file",
"//internal/grpc/dialer",
"//internal/grpc/grpclog",
"//internal/grpc/retry",
"//internal/helm",
"//internal/kms/uri",
Expand Down Expand Up @@ -163,6 +163,7 @@ go_test(
"//internal/cloud/gcpshared",
"//internal/config",
"//internal/constants",
"//internal/constellation",
"//internal/crypto",
"//internal/crypto/testvector",
"//internal/file",
Expand Down
23 changes: 22 additions & 1 deletion cli/internal/cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"strings"
"time"

"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/atls"
Expand All @@ -31,6 +32,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/kubecmd"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/edgelesssys/constellation/v2/internal/versions"
Expand Down Expand Up @@ -252,12 +254,13 @@ func runApply(cmd *cobra.Command, _ []string) error {
)
}

applier := constellation.NewApplier(log)
applier := constellation.NewApplier(log, spinner, newDialer)

apply := &applyCmd{
fileHandler: fileHandler,
flags: flags,
log: log,
wLog: &cloudcmd.WarnLogger{Cmd: cmd, Log: log},
spinner: spinner,
merger: &kubeconfigMerger{log: log},
newHelmClient: newHelmClient,
Expand All @@ -279,6 +282,7 @@ type applyCmd struct {
flags applyFlags

log debugLog
wLog warnLog
spinner spinnerInterf

merger configMerger
Expand All @@ -293,6 +297,23 @@ type applyCmd struct {

type applier interface {
CheckLicense(ctx context.Context, csp cloudprovider.Provider, licenseID string) (int, error)
GenerateMasterSecret() (uri.MasterSecret, error)
GenerateMeasurementSalt() ([]byte, error)
Init(
ctx context.Context,
validator atls.Validator,
state *state.State,
clusterLogWriter io.Writer,
payload constellation.InitPayload,
) (
*initproto.InitSuccessResponse,
error,
)
}

type warnLog interface {
Warnf(format string, args ...any)
Infof(format string, args ...any)
}

/*
Expand Down
25 changes: 23 additions & 2 deletions cli/internal/cmd/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ import (
"context"
"errors"
"fmt"
"io"
"path/filepath"
"strings"
"testing"
"time"

"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/constellation"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/helm"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
Expand Down Expand Up @@ -488,8 +492,25 @@ func newPhases(phases ...skipPhase) skipPhases {
return skipPhases
}

type stubConstellApplier struct{}
type stubConstellApplier struct {
checkLicenseErr error
generateMasterSecretErr error
generateMeasurementSaltErr error
initErr error
}

func (s *stubConstellApplier) CheckLicense(context.Context, cloudprovider.Provider, string) (int, error) {
return 0, nil
return 0, s.checkLicenseErr
}

func (s *stubConstellApplier) GenerateMasterSecret() (uri.MasterSecret, error) {
return uri.MasterSecret{}, s.generateMasterSecretErr
}

func (s *stubConstellApplier) GenerateMeasurementSalt() ([]byte, error) {
return nil, s.generateMeasurementSaltErr
}

func (s *stubConstellApplier) Init(context.Context, atls.Validator, *state.State, io.Writer, constellation.InitPayload) (*initproto.InitSuccessResponse, error) {
return nil, s.initErr
}
102 changes: 27 additions & 75 deletions cli/internal/cmd/applyinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,23 @@ package cmd

import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"net/url"
"path/filepath"
"strconv"
"text/tabwriter"
"time"

"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"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/constellation"
"github.com/edgelesssys/constellation/v2/internal/file"
grpcRetry "github.com/edgelesssys/constellation/v2/internal/grpc/retry"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/retry"
"github.com/edgelesssys/constellation/v2/internal/state"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
)
Expand All @@ -41,51 +34,45 @@ import (
// On success, it writes the Kubernetes admin config file to disk.
// Therefore it is skipped if the Kubernetes admin config file already exists.
func (a *applyCmd) runInit(cmd *cobra.Command, conf *config.Config, stateFile *state.State) (*bytes.Buffer, error) {
a.log.Debugf("Running init RPC")
a.log.Debugf("Creating aTLS Validator for %s", conf.GetAttestationConfig().GetVariant())
validator, err := cloudcmd.NewValidator(cmd, conf.GetAttestationConfig(), a.log)
validator, err := choose.Validator(conf.GetAttestationConfig(), a.wLog)
if err != nil {
return nil, fmt.Errorf("creating new validator: %w", err)
return nil, fmt.Errorf("creating validator: %w", err)
}

a.log.Debugf("Generating master secret")
a.log.Debugf("Running init RPC")
masterSecret, err := a.generateAndPersistMasterSecret(cmd.OutOrStdout())
if err != nil {
return nil, fmt.Errorf("generating master secret: %w", err)
}
a.log.Debugf("Generated master secret key and salt values")

a.log.Debugf("Generating measurement salt")
measurementSalt, err := crypto.GenerateRandomBytes(crypto.RNGLengthDefault)
measurementSalt, err := a.applier.GenerateMeasurementSalt()
if err != nil {
return nil, fmt.Errorf("generating measurement salt: %w", err)
}

a.spinner.Start("Connecting ", false)
req := &initproto.InitRequest{
KmsUri: masterSecret.EncodeToURI(),
StorageUri: uri.NoStoreURI,
MeasurementSalt: measurementSalt,
KubernetesVersion: versions.VersionConfigs[conf.KubernetesVersion].ClusterVersion,
KubernetesComponents: versions.VersionConfigs[conf.KubernetesVersion].KubernetesComponents.ToInitProto(),
ConformanceMode: a.flags.conformance,
InitSecret: stateFile.Infrastructure.InitSecret,
ClusterName: stateFile.Infrastructure.Name,
ApiserverCertSans: stateFile.Infrastructure.APIServerCertSANs,
ServiceCidr: conf.ServiceCIDR,
}
a.log.Debugf("Sending initialization request")
resp, err := a.initCall(cmd.Context(), a.newDialer(validator), stateFile.Infrastructure.ClusterEndpoint, req)
a.spinner.Stop()
a.log.Debugf("Initialization request finished")

clusterLogs := &bytes.Buffer{}
resp, err := a.applier.Init(
cmd.Context(), validator, stateFile, clusterLogs,
constellation.InitPayload{
MasterSecret: masterSecret,
MeasurementSalt: measurementSalt,
K8sVersion: conf.KubernetesVersion,
ConformanceMode: a.flags.conformance,
ServiceCIDR: conf.ServiceCIDR,
})
if len(clusterLogs.Bytes()) > 0 {
if err := a.fileHandler.Write(constants.ErrorLog, clusterLogs.Bytes(), file.OptAppend); err != nil {
return nil, fmt.Errorf("writing bootstrapper logs: %w", err)
}
}
if err != nil {
var nonRetriable *nonRetriableError
var nonRetriable *constellation.NonRetriableInitError
if errors.As(err, &nonRetriable) {
cmd.PrintErrln("Cluster initialization failed. This error is not recoverable.")
cmd.PrintErrln("Terminate your cluster and try again.")
if nonRetriable.logCollectionErr != nil {
cmd.PrintErrf("Failed to collect logs from bootstrapper: %s\n", nonRetriable.logCollectionErr)
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", a.flags.pathPrefixer.PrefixPrintablePath(constants.ErrorLog))
}
Expand All @@ -103,49 +90,14 @@ func (a *applyCmd) runInit(cmd *cobra.Command, conf *config.Config, stateFile *s
return bufferedOutput, nil
}

// initCall performs the gRPC call to the bootstrapper to initialize the cluster.
func (a *applyCmd) initCall(ctx context.Context, dialer grpcDialer, ip string, req *initproto.InitRequest) (*initproto.InitSuccessResponse, error) {
doer := &initDoer{
dialer: dialer,
endpoint: net.JoinHostPort(ip, strconv.Itoa(constants.BootstrapperPort)),
req: req,
log: a.log,
spinner: a.spinner,
fh: file.NewHandler(afero.NewOsFs()),
}

// 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)
a.log.Debugf("Encountered error (retriable: %t): %s", isServiceUnavailable, err)
return isServiceUnavailable
}

a.log.Debugf("Making initialization call, doer is %+v", doer)
retrier := retry.NewIntervalRetrier(doer, 30*time.Second, serviceIsUnavailable)
if err := retrier.Do(ctx); err != nil {
return nil, err
}
return doer.resp, nil
}

// generateAndPersistMasterSecret generates a 32 byte master secret and saves it to disk.
func (a *applyCmd) generateAndPersistMasterSecret(outWriter io.Writer) (uri.MasterSecret, error) {
// No file given, generate a new secret, and save it to disk
key, err := crypto.GenerateRandomBytes(crypto.MasterSecretLengthDefault)
if err != nil {
return uri.MasterSecret{}, err
}
salt, err := crypto.GenerateRandomBytes(crypto.RNGLengthDefault)
secret, err := a.applier.GenerateMasterSecret()
if err != nil {
return uri.MasterSecret{}, err
}
secret := uri.MasterSecret{
Key: key,
Salt: salt,
return uri.MasterSecret{}, fmt.Errorf("generating master secret: %w", err)
}
if err := a.fileHandler.WriteJSON(constants.MasterSecretFilename, secret, file.OptNone); err != nil {
return uri.MasterSecret{}, err
return uri.MasterSecret{}, fmt.Errorf("writing master secret: %w", err)
}
fmt.Fprintf(outWriter, "Your Constellation master secret was successfully written to %q\n", a.flags.pathPrefixer.PrefixPrintablePath(constants.MasterSecretFilename))
return secret, nil
Expand Down
Loading

0 comments on commit 17aecaa

Please sign in to comment.