mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
initserver: add client verification
This commit is contained in:
parent
bffa5c580c
commit
3b6bc3b28f
@ -21,11 +21,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Environment variable `CONSTELL_AZURE_CLIENT_SECRET_VALUE` as an alternative way to provide the configuration value `provider.azure.clientSecretValue`.
|
||||
|
||||
- Automatic CSI driver deployment for Azure and GCP during Constellation init
|
||||
|
||||
- Improve reproducibility by pinning the Kubernetes components.
|
||||
- Client verification during `constellation init`
|
||||
|
||||
### Changed
|
||||
<!-- For changes in existing functionality. -->
|
||||
@ -51,7 +51,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- `constellation create` on GCP now always uses the local default credentials.
|
||||
|
||||
|
||||
## [2.2.2] - 2022-11-17
|
||||
|
||||
### Fixed
|
||||
@ -69,6 +68,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Security
|
||||
|
||||
Vulnerabilities in `kube-apiserver` fixed by upgrading to v1.23.14, v1.24.8 and v1.25.4:
|
||||
|
||||
- [CVE-2022-3162](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-3162)
|
||||
- [CVE-2022-3294](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-3294)
|
||||
|
||||
@ -135,7 +135,9 @@ Vulnerabilities in `kube-apiserver` fixed by upgrading to v1.23.14, v1.24.8 and
|
||||
### Fixed
|
||||
|
||||
### Security
|
||||
|
||||
Vulnerability inside the Go standard library fixed by updating to Go 1.19.2:
|
||||
|
||||
- [GO-2022-1037](https://pkg.go.dev/vuln/GO-2022-1037) ([CVE-2022-2879](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-2879))
|
||||
- [GO-2022-1038](https://pkg.go.dev/vuln/GO-2022-1038) ([CVE-2022-2880](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-2880))
|
||||
- [GO-2022-0969](https://pkg.go.dev/vuln/GO-2022-0969) ([CVE-2022-27664](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-27664))
|
||||
|
@ -56,7 +56,10 @@ func run(issuerWrapper initserver.IssuerWrapper, tpm vtpm.TPMOpenFunc, fileHandl
|
||||
}
|
||||
|
||||
nodeLock := nodelock.New(tpm)
|
||||
initServer := initserver.New(nodeLock, kube, issuerWrapper, fileHandler, log)
|
||||
initServer, err := initserver.New(context.Background(), nodeLock, kube, issuerWrapper, fileHandler, metadata, log)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to create init server")
|
||||
}
|
||||
|
||||
dialer := dialer.New(issuerWrapper, nil, &net.Dialer{})
|
||||
joinClient := joinclient.New(nodeLock, dialer, kube, metadata, log)
|
||||
@ -92,5 +95,6 @@ type clusterInitJoiner interface {
|
||||
|
||||
type metadataAPI interface {
|
||||
joinclient.MetadataAPI
|
||||
initserver.MetadataAPI
|
||||
GetLoadBalancerEndpoint(ctx context.Context) (string, error)
|
||||
}
|
||||
|
@ -56,3 +56,7 @@ func (f *providerMetadataFake) Self(ctx context.Context) (metadata.InstanceMetad
|
||||
func (f *providerMetadataFake) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *providerMetadataFake) InitSecretHash(ctx context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ type InitRequest struct {
|
||||
EnforceIdkeydigest bool `protobuf:"varint,13,opt,name=enforce_idkeydigest,json=enforceIdkeydigest,proto3" json:"enforce_idkeydigest,omitempty"`
|
||||
ConformanceMode bool `protobuf:"varint,14,opt,name=conformance_mode,json=conformanceMode,proto3" json:"conformance_mode,omitempty"`
|
||||
KubernetesComponents []*KubernetesComponent `protobuf:"bytes,15,rep,name=kubernetes_components,json=kubernetesComponents,proto3" json:"kubernetes_components,omitempty"`
|
||||
InitSecret []byte `protobuf:"bytes,16,opt,name=init_secret,json=initSecret,proto3" json:"init_secret,omitempty"`
|
||||
}
|
||||
|
||||
func (x *InitRequest) Reset() {
|
||||
@ -165,6 +166,13 @@ func (x *InitRequest) GetKubernetesComponents() []*KubernetesComponent {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *InitRequest) GetInitSecret() []byte {
|
||||
if x != nil {
|
||||
return x.InitSecret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InitResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -303,7 +311,7 @@ var File_init_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_init_proto_rawDesc = []byte{
|
||||
0x0a, 0x0a, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x69, 0x6e,
|
||||
0x69, 0x74, 0x22, 0xc3, 0x04, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x69, 0x74, 0x22, 0xe4, 0x04, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63,
|
||||
0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65,
|
||||
0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6b, 0x6d, 0x73, 0x5f, 0x75,
|
||||
@ -339,29 +347,31 @@ var file_init_proto_rawDesc = []byte{
|
||||
0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x4b,
|
||||
0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65,
|
||||
0x6e, 0x74, 0x52, 0x14, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x43, 0x6f,
|
||||
0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x68, 0x0a, 0x0c, 0x49, 0x6e, 0x69, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6b, 0x75, 0x62, 0x65,
|
||||
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6b, 0x75,
|
||||
0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, 0x6e, 0x65,
|
||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x65,
|
||||
0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
|
||||
0x49, 0x64, 0x22, 0x78, 0x0a, 0x13, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73,
|
||||
0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68,
|
||||
0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12,
|
||||
0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x50, 0x61,
|
||||
0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x32, 0x34, 0x0a, 0x03,
|
||||
0x41, 0x50, 0x49, 0x12, 0x2d, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x69, 0x6e,
|
||||
0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,
|
||||
0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f, 0x6e,
|
||||
0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x62, 0x6f,
|
||||
0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x69, 0x74,
|
||||
0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x69,
|
||||
0x6e, 0x69, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0x68, 0x0a, 0x0c, 0x49, 0x6e, 0x69,
|
||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6b, 0x75, 0x62,
|
||||
0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6b,
|
||||
0x75, 0x62, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x77, 0x6e,
|
||||
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x77, 0x6e,
|
||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
|
||||
0x72, 0x49, 0x64, 0x22, 0x78, 0x0a, 0x13, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65,
|
||||
0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72,
|
||||
0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68,
|
||||
0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x50,
|
||||
0x61, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x32, 0x34, 0x0a,
|
||||
0x03, 0x41, 0x50, 0x49, 0x12, 0x2d, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x69,
|
||||
0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x12, 0x2e, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x65, 0x64, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x73, 0x73, 0x79, 0x73, 0x2f, 0x63, 0x6f,
|
||||
0x6e, 0x73, 0x74, 0x65, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x62,
|
||||
0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x69, 0x74,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -24,6 +24,7 @@ message InitRequest {
|
||||
bool enforce_idkeydigest = 13;
|
||||
bool conformance_mode = 14;
|
||||
repeated KubernetesComponent kubernetes_components = 15;
|
||||
bytes init_secret = 16;
|
||||
}
|
||||
|
||||
message InitResponse {
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
@ -45,22 +46,33 @@ type Server struct {
|
||||
cleaner cleaner
|
||||
issuerWrapper IssuerWrapper
|
||||
|
||||
initSecretHash []byte
|
||||
|
||||
log *logger.Logger
|
||||
|
||||
initproto.UnimplementedAPIServer
|
||||
}
|
||||
|
||||
// New creates a new initialization server.
|
||||
func New(lock locker, kube ClusterInitializer, issuerWrapper IssuerWrapper, fh file.Handler, log *logger.Logger) *Server {
|
||||
func New(ctx context.Context, lock locker, kube ClusterInitializer, issuerWrapper IssuerWrapper, fh file.Handler, metadata MetadataAPI, log *logger.Logger) (*Server, error) {
|
||||
log = log.Named("initServer")
|
||||
|
||||
initSecretHash, err := metadata.InitSecretHash(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving init secret hash: %w", err)
|
||||
}
|
||||
if len(initSecretHash) == 0 {
|
||||
return nil, fmt.Errorf("init secret hash is empty")
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
nodeLock: lock,
|
||||
disk: diskencryption.New(),
|
||||
initializer: kube,
|
||||
fileHandler: fh,
|
||||
issuerWrapper: issuerWrapper,
|
||||
log: log,
|
||||
nodeLock: lock,
|
||||
disk: diskencryption.New(),
|
||||
initializer: kube,
|
||||
fileHandler: fh,
|
||||
issuerWrapper: issuerWrapper,
|
||||
log: log,
|
||||
initSecretHash: initSecretHash,
|
||||
}
|
||||
|
||||
grpcServer := grpc.NewServer(
|
||||
@ -71,7 +83,7 @@ func New(lock locker, kube ClusterInitializer, issuerWrapper IssuerWrapper, fh f
|
||||
initproto.RegisterAPIServer(grpcServer, server)
|
||||
|
||||
server.grpcServer = grpcServer
|
||||
return server
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// Serve starts the initialization server.
|
||||
@ -92,6 +104,10 @@ func (s *Server) Init(ctx context.Context, req *initproto.InitRequest) (*initpro
|
||||
log := s.log.With(zap.String("peer", grpclog.PeerAddrFromContext(ctx)))
|
||||
log.Infof("Init called")
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword(s.initSecretHash, req.InitSecret); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "invalid init secret %s", err)
|
||||
}
|
||||
|
||||
// generate values for cluster attestation
|
||||
measurementSalt, clusterID, err := deriveMeasurementValues(req.MasterSecret, req.Salt)
|
||||
if err != nil {
|
||||
@ -267,3 +283,9 @@ type locker interface {
|
||||
type cleaner interface {
|
||||
Clean()
|
||||
}
|
||||
|
||||
// MetadataAPI provides information about the instances.
|
||||
type MetadataAPI interface {
|
||||
// InitSecretHash returns the initSecretHash of the instance.
|
||||
InitSecretHash(ctx context.Context) ([]byte, error)
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -31,17 +32,45 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
fh := file.NewHandler(afero.NewMemMapFs())
|
||||
server := New(newFakeLock(), &stubClusterInitializer{}, IssuerWrapper{}, fh, logger.NewTest(t))
|
||||
assert.NotNil(server)
|
||||
assert.NotNil(server.log)
|
||||
assert.NotNil(server.nodeLock)
|
||||
assert.NotNil(server.initializer)
|
||||
assert.NotNil(server.grpcServer)
|
||||
assert.NotNil(server.fileHandler)
|
||||
assert.NotNil(server.disk)
|
||||
|
||||
testCases := map[string]struct {
|
||||
metadata stubMetadata
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
metadata: stubMetadata{initSecretHashVal: []byte("hash")},
|
||||
},
|
||||
"empty init secret hash": {
|
||||
metadata: stubMetadata{initSecretHashVal: nil},
|
||||
wantErr: true,
|
||||
},
|
||||
"metadata error": {
|
||||
metadata: stubMetadata{initSecretHashErr: errors.New("error")},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
server, err := New(context.TODO(), newFakeLock(), &stubClusterInitializer{}, IssuerWrapper{}, fh, &tc.metadata, logger.NewTest(t))
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.NotNil(server)
|
||||
assert.NotEmpty(server.initSecretHash)
|
||||
assert.NotNil(server.log)
|
||||
assert.NotNil(server.nodeLock)
|
||||
assert.NotNil(server.initializer)
|
||||
assert.NotNil(server.grpcServer)
|
||||
assert.NotNil(server.fileHandler)
|
||||
assert.NotNil(server.disk)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
@ -51,70 +80,91 @@ func TestInit(t *testing.T) {
|
||||
require.True(t, aqcuiredLock)
|
||||
require.Nil(t, lockErr)
|
||||
|
||||
initSecret := []byte("password")
|
||||
initSecretHash, err := bcrypt.GenerateFromPassword(initSecret, bcrypt.DefaultCost)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
nodeLock *fakeLock
|
||||
initializer ClusterInitializer
|
||||
disk encryptedDisk
|
||||
fileHandler file.Handler
|
||||
req *initproto.InitRequest
|
||||
wantErr bool
|
||||
wantShutdown bool
|
||||
nodeLock *fakeLock
|
||||
initializer ClusterInitializer
|
||||
disk encryptedDisk
|
||||
fileHandler file.Handler
|
||||
req *initproto.InitRequest
|
||||
initSecretHash []byte
|
||||
wantErr bool
|
||||
wantShutdown bool
|
||||
}{
|
||||
"successful init": {
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{},
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
initSecretHash: initSecretHash,
|
||||
req: &initproto.InitRequest{InitSecret: initSecret},
|
||||
},
|
||||
"node locked": {
|
||||
nodeLock: lockedLock,
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{},
|
||||
wantErr: true,
|
||||
wantShutdown: true,
|
||||
nodeLock: lockedLock,
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{InitSecret: initSecret},
|
||||
initSecretHash: initSecretHash,
|
||||
wantErr: true,
|
||||
wantShutdown: true,
|
||||
},
|
||||
"disk open error": {
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{openErr: someErr},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{},
|
||||
wantErr: true,
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{openErr: someErr},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{InitSecret: initSecret},
|
||||
initSecretHash: initSecretHash,
|
||||
wantErr: true,
|
||||
},
|
||||
"disk uuid error": {
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{uuidErr: someErr},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{},
|
||||
wantErr: true,
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{uuidErr: someErr},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{InitSecret: initSecret},
|
||||
initSecretHash: initSecretHash,
|
||||
wantErr: true,
|
||||
},
|
||||
"disk update passphrase error": {
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{updatePassphraseErr: someErr},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{},
|
||||
wantErr: true,
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{updatePassphraseErr: someErr},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{InitSecret: initSecret},
|
||||
initSecretHash: initSecretHash,
|
||||
wantErr: true,
|
||||
},
|
||||
"write state file error": {
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewReadOnlyFs(afero.NewMemMapFs())),
|
||||
req: &initproto.InitRequest{},
|
||||
wantErr: true,
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewReadOnlyFs(afero.NewMemMapFs())),
|
||||
req: &initproto.InitRequest{InitSecret: initSecret},
|
||||
initSecretHash: initSecretHash,
|
||||
wantErr: true,
|
||||
},
|
||||
"initialize cluster error": {
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{initClusterErr: someErr},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{},
|
||||
wantErr: true,
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{initClusterErr: someErr},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
req: &initproto.InitRequest{InitSecret: initSecret},
|
||||
initSecretHash: initSecretHash,
|
||||
wantErr: true,
|
||||
},
|
||||
"wrong initSecret": {
|
||||
nodeLock: newFakeLock(),
|
||||
initializer: &stubClusterInitializer{},
|
||||
disk: &stubDisk{},
|
||||
fileHandler: file.NewHandler(afero.NewMemMapFs()),
|
||||
initSecretHash: initSecretHash,
|
||||
req: &initproto.InitRequest{InitSecret: []byte("wrongpassword")},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -124,13 +174,14 @@ func TestInit(t *testing.T) {
|
||||
|
||||
serveStopper := newStubServeStopper()
|
||||
server := &Server{
|
||||
nodeLock: tc.nodeLock,
|
||||
initializer: tc.initializer,
|
||||
disk: tc.disk,
|
||||
fileHandler: tc.fileHandler,
|
||||
log: logger.NewTest(t),
|
||||
grpcServer: serveStopper,
|
||||
cleaner: &fakeCleaner{serveStopper: serveStopper},
|
||||
nodeLock: tc.nodeLock,
|
||||
initializer: tc.initializer,
|
||||
disk: tc.disk,
|
||||
fileHandler: tc.fileHandler,
|
||||
log: logger.NewTest(t),
|
||||
grpcServer: serveStopper,
|
||||
cleaner: &fakeCleaner{serveStopper: serveStopper},
|
||||
initSecretHash: tc.initSecretHash,
|
||||
}
|
||||
|
||||
kubeconfig, err := server.Init(context.Background(), tc.req)
|
||||
@ -293,3 +344,12 @@ type fakeCleaner struct {
|
||||
func (f *fakeCleaner) Clean() {
|
||||
go f.serveStopper.GracefulStop() // this is not the correct way to do this, but it's fine for testing
|
||||
}
|
||||
|
||||
type stubMetadata struct {
|
||||
initSecretHashVal []byte
|
||||
initSecretHashErr error
|
||||
}
|
||||
|
||||
func (m *stubMetadata) InitSecretHash(context.Context) ([]byte, error) {
|
||||
return m.initSecretHashVal, m.initSecretHashErr
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
|
||||
type terraformClient interface {
|
||||
PrepareWorkspace(provider cloudprovider.Provider, input terraform.Variables) error
|
||||
CreateCluster(ctx context.Context) (string, error)
|
||||
CreateCluster(ctx context.Context) (string, string, error)
|
||||
DestroyCluster(ctx context.Context) error
|
||||
CleanUpWorkspace() error
|
||||
RemoveInstaller()
|
||||
|
@ -27,6 +27,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
type stubTerraformClient struct {
|
||||
ip string
|
||||
initSecret string
|
||||
cleanUpWorkspaceCalled bool
|
||||
removeInstallerCalled bool
|
||||
destroyClusterCalled bool
|
||||
@ -36,8 +37,8 @@ type stubTerraformClient struct {
|
||||
cleanUpWorkspaceErr error
|
||||
}
|
||||
|
||||
func (c *stubTerraformClient) CreateCluster(ctx context.Context) (string, error) {
|
||||
return c.ip, c.createClusterErr
|
||||
func (c *stubTerraformClient) CreateCluster(ctx context.Context) (string, string, error) {
|
||||
return c.ip, c.initSecret, c.createClusterErr
|
||||
}
|
||||
|
||||
func (c *stubTerraformClient) PrepareWorkspace(provider cloudprovider.Provider, input terraform.Variables) error {
|
||||
|
@ -122,13 +122,14 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *con
|
||||
}
|
||||
|
||||
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
||||
ip, err := cl.CreateCluster(ctx)
|
||||
ip, initSecret, err := cl.CreateCluster(ctx)
|
||||
if err != nil {
|
||||
return clusterid.File{}, err
|
||||
}
|
||||
|
||||
return clusterid.File{
|
||||
CloudProvider: cloudprovider.AWS,
|
||||
InitSecret: []byte(initSecret),
|
||||
IP: ip,
|
||||
}, nil
|
||||
}
|
||||
@ -158,13 +159,14 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
|
||||
}
|
||||
|
||||
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
||||
ip, err := cl.CreateCluster(ctx)
|
||||
ip, initSecret, err := cl.CreateCluster(ctx)
|
||||
if err != nil {
|
||||
return clusterid.File{}, err
|
||||
}
|
||||
|
||||
return clusterid.File{
|
||||
CloudProvider: cloudprovider.GCP,
|
||||
InitSecret: []byte(initSecret),
|
||||
IP: ip,
|
||||
}, nil
|
||||
}
|
||||
@ -197,7 +199,7 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
|
||||
}
|
||||
|
||||
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
|
||||
ip, err := cl.CreateCluster(ctx)
|
||||
ip, initSecret, err := cl.CreateCluster(ctx)
|
||||
if err != nil {
|
||||
return clusterid.File{}, err
|
||||
}
|
||||
@ -205,6 +207,7 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
|
||||
return clusterid.File{
|
||||
CloudProvider: cloudprovider.Azure,
|
||||
IP: ip,
|
||||
InitSecret: []byte(initSecret),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -309,13 +312,14 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
|
||||
// Allow rollback of QEMU Terraform workspace from this point on
|
||||
qemuRollbacker.createdWorkspace = true
|
||||
|
||||
ip, err := cl.CreateCluster(ctx)
|
||||
ip, initSecret, err := cl.CreateCluster(ctx)
|
||||
if err != nil {
|
||||
return clusterid.File{}, err
|
||||
}
|
||||
|
||||
return clusterid.File{
|
||||
CloudProvider: cloudprovider.QEMU,
|
||||
InitSecret: []byte(initSecret),
|
||||
IP: ip,
|
||||
}, nil
|
||||
}
|
||||
|
@ -22,4 +22,6 @@ type File struct {
|
||||
CloudProvider cloudprovider.Provider `json:"cloudprovider,omitempty"`
|
||||
// IP is the IP address the cluster can be reached at (often the load balancer).
|
||||
IP string `json:"ip,omitempty"`
|
||||
// InitSecret is the secret the first Bootstrapper uses to verify the user.
|
||||
InitSecret []byte `json:"initsecret,omitempty"`
|
||||
}
|
||||
|
@ -138,6 +138,7 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
|
||||
EnforcedPcrs: conf.EnforcedPCRs(),
|
||||
EnforceIdkeydigest: conf.EnforcesIDKeyDigest(),
|
||||
ConformanceMode: flags.conformance,
|
||||
InitSecret: idFile.InitSecret,
|
||||
}
|
||||
resp, err := initCall(cmd.Context(), newDialer(validator), idFile.IP, req)
|
||||
spinner.Stop()
|
||||
|
@ -74,30 +74,39 @@ func (c *Client) PrepareWorkspace(provider cloudprovider.Provider, vars Variable
|
||||
}
|
||||
|
||||
// CreateCluster creates a Constellation cluster using Terraform.
|
||||
func (c *Client) CreateCluster(ctx context.Context) (string, error) {
|
||||
func (c *Client) CreateCluster(ctx context.Context) (string, string, error) {
|
||||
if err := c.tf.Init(ctx); err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err := c.tf.Apply(ctx); err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
tfState, err := c.tf.Show(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
ipOutput, ok := tfState.Values.Outputs["ip"]
|
||||
if !ok {
|
||||
return "", errors.New("no IP output found")
|
||||
return "", "", errors.New("no IP output found")
|
||||
}
|
||||
ip, ok := ipOutput.Value.(string)
|
||||
if !ok {
|
||||
return "", errors.New("invalid type in IP output: not a string")
|
||||
return "", "", errors.New("invalid type in IP output: not a string")
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
secretOutput, ok := tfState.Values.Outputs["initSecret"]
|
||||
if !ok {
|
||||
return "", "", errors.New("no initSecret output found")
|
||||
}
|
||||
secret, ok := secretOutput.Value.(string)
|
||||
if !ok {
|
||||
return "", "", errors.New("invalid type in initSecret output: not a string")
|
||||
}
|
||||
|
||||
return ip, secret, nil
|
||||
}
|
||||
|
||||
// DestroyCluster destroys a Constellation cluster using Terraform.
|
||||
|
@ -19,6 +19,7 @@ provider "aws" {
|
||||
locals {
|
||||
uid = random_id.uid.hex
|
||||
name = "${var.name}-${local.uid}"
|
||||
initSecretHash = random_password.initSecret.bcrypt_hash
|
||||
ports_node_range = "30000-32767"
|
||||
ports_kubernetes = "6443"
|
||||
ports_bootstrapper = "9000"
|
||||
@ -34,6 +35,12 @@ resource "random_id" "uid" {
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "random_password" "initSecret" {
|
||||
length = 32
|
||||
special = true
|
||||
override_special = "_%@"
|
||||
}
|
||||
|
||||
resource "aws_vpc" "vpc" {
|
||||
cidr_block = "192.168.0.0/16"
|
||||
tags = merge(local.tags, { Name = "${local.name}-vpc" })
|
||||
@ -230,6 +237,14 @@ module "instance_group_control_plane" {
|
||||
security_groups = [aws_security_group.security_group.id]
|
||||
subnetwork = module.public_private_subnet.private_subnet_id
|
||||
iam_instance_profile = var.iam_instance_profile_control_plane
|
||||
tags = merge(
|
||||
local.tags,
|
||||
{ Name = local.name },
|
||||
{ constellation-role = "control-plane" },
|
||||
{ constellation-uid = local.uid },
|
||||
{ KubernetesCluster = "Constellation-${local.uid}" },
|
||||
{ constellation-init-secret-hash = local.initSecretHash }
|
||||
)
|
||||
}
|
||||
|
||||
module "instance_group_worker_nodes" {
|
||||
@ -246,4 +261,12 @@ module "instance_group_worker_nodes" {
|
||||
target_group_arns = []
|
||||
security_groups = [aws_security_group.security_group.id]
|
||||
iam_instance_profile = var.iam_instance_profile_worker_nodes
|
||||
tags = merge(
|
||||
local.tags,
|
||||
{ Name = local.name },
|
||||
{ constellation-role = "worker" },
|
||||
{ constellation-uid = local.uid },
|
||||
{ KubernetesCluster = "Constellation-${local.uid}" },
|
||||
{ constellation-init-secret-hash = local.initSecretHash }
|
||||
)
|
||||
}
|
||||
|
@ -57,26 +57,13 @@ resource "aws_autoscaling_group" "autoscaling_group" {
|
||||
vpc_zone_identifier = [var.subnetwork]
|
||||
target_group_arns = var.target_group_arns
|
||||
|
||||
tag {
|
||||
key = "Name"
|
||||
value = local.name
|
||||
propagate_at_launch = true
|
||||
}
|
||||
tag {
|
||||
key = "constellation-role"
|
||||
value = var.role
|
||||
propagate_at_launch = true
|
||||
}
|
||||
tag {
|
||||
key = "constellation-uid"
|
||||
value = var.uid
|
||||
propagate_at_launch = true
|
||||
}
|
||||
|
||||
tag {
|
||||
key = "KubernetesCluster"
|
||||
value = "Constellation-${var.uid}"
|
||||
propagate_at_launch = true
|
||||
dynamic "tag" {
|
||||
for_each = var.tags
|
||||
content {
|
||||
key = tag.key
|
||||
value = tag.value
|
||||
propagate_at_launch = true
|
||||
}
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
|
@ -57,3 +57,8 @@ variable "security_groups" {
|
||||
type = list(string)
|
||||
description = "List of IDs of the security groups for an instance."
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
type = map(string)
|
||||
description = "The tags to add to the instance group."
|
||||
}
|
||||
|
@ -1,3 +1,8 @@
|
||||
output "ip" {
|
||||
value = aws_eip.lb.public_ip
|
||||
}
|
||||
|
||||
output "initSecret" {
|
||||
value = random_password.initSecret.result
|
||||
sensitive = true
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ provider "azurerm" {
|
||||
locals {
|
||||
uid = random_id.uid.hex
|
||||
name = "${var.name}-${local.uid}"
|
||||
initSecretHash = random_password.initSecret.bcrypt_hash
|
||||
tags = { constellation-uid = local.uid }
|
||||
ports_node_range = "30000-32767"
|
||||
ports_kubernetes = "6443"
|
||||
@ -34,6 +35,12 @@ resource "random_id" "uid" {
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "random_password" "initSecret" {
|
||||
length = 32
|
||||
special = true
|
||||
override_special = "_%@"
|
||||
}
|
||||
|
||||
resource "azurerm_application_insights" "insights" {
|
||||
name = local.name
|
||||
location = var.location
|
||||
@ -194,7 +201,7 @@ module "scale_set_control_plane" {
|
||||
instance_type = var.instance_type
|
||||
confidential_vm = var.confidential_vm
|
||||
secure_boot = var.secure_boot
|
||||
tags = merge(local.tags, { constellation-role = "control-plane" })
|
||||
tags = merge(local.tags, { constellation-role = "control-plane" }, { constellation-init-secret-hash = local.initSecretHash })
|
||||
image_id = var.image_id
|
||||
user_assigned_identity = var.user_assigned_identity
|
||||
network_security_group_id = azurerm_network_security_group.security_group.id
|
||||
@ -217,7 +224,7 @@ module "scale_set_worker" {
|
||||
instance_type = var.instance_type
|
||||
confidential_vm = var.confidential_vm
|
||||
secure_boot = var.secure_boot
|
||||
tags = merge(local.tags, { constellation-role = "worker" })
|
||||
tags = merge(local.tags, { constellation-role = "worker" }, { constellation-init-secret-hash = local.initSecretHash })
|
||||
image_id = var.image_id
|
||||
user_assigned_identity = var.user_assigned_identity
|
||||
network_security_group_id = azurerm_network_security_group.security_group.id
|
||||
|
@ -1,3 +1,8 @@
|
||||
output "ip" {
|
||||
value = azurerm_public_ip.loadbalancer_ip.ip_address
|
||||
}
|
||||
|
||||
output "initSecret" {
|
||||
value = random_password.initSecret.result
|
||||
sensitive = true
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ provider "google" {
|
||||
locals {
|
||||
uid = random_id.uid.hex
|
||||
name = "${var.name}-${local.uid}"
|
||||
initSecretHash = random_password.initSecret.bcrypt_hash
|
||||
labels = { constellation-uid = local.uid }
|
||||
ports_node_range = "30000-32767"
|
||||
ports_kubernetes = "6443"
|
||||
@ -37,6 +38,12 @@ resource "random_id" "uid" {
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "random_password" "initSecret" {
|
||||
length = 32
|
||||
special = true
|
||||
override_special = "_%@"
|
||||
}
|
||||
|
||||
resource "google_compute_network" "vpc_network" {
|
||||
name = local.name
|
||||
description = "Constellation VPC network"
|
||||
@ -136,24 +143,26 @@ module "instance_group_control_plane" {
|
||||
{ name = "recovery", port = local.ports_recovery },
|
||||
var.debug ? [{ name = "debugd", port = local.ports_debugd }] : [],
|
||||
])
|
||||
labels = local.labels
|
||||
labels = local.labels
|
||||
init_secret_hash = local.initSecretHash
|
||||
}
|
||||
|
||||
module "instance_group_worker" {
|
||||
source = "./modules/instance_group"
|
||||
name = local.name
|
||||
role = "Worker"
|
||||
uid = local.uid
|
||||
instance_type = var.instance_type
|
||||
instance_count = var.worker_count
|
||||
image_id = var.image_id
|
||||
disk_size = var.state_disk_size
|
||||
disk_type = var.state_disk_type
|
||||
network = google_compute_network.vpc_network.id
|
||||
subnetwork = google_compute_subnetwork.vpc_subnetwork.id
|
||||
kube_env = local.kube_env
|
||||
debug = var.debug
|
||||
labels = local.labels
|
||||
source = "./modules/instance_group"
|
||||
name = local.name
|
||||
role = "Worker"
|
||||
uid = local.uid
|
||||
instance_type = var.instance_type
|
||||
instance_count = var.worker_count
|
||||
image_id = var.image_id
|
||||
disk_size = var.state_disk_size
|
||||
disk_type = var.state_disk_type
|
||||
network = google_compute_network.vpc_network.id
|
||||
subnetwork = google_compute_subnetwork.vpc_subnetwork.id
|
||||
kube_env = local.kube_env
|
||||
debug = var.debug
|
||||
labels = local.labels
|
||||
init_secret_hash = local.initSecretHash
|
||||
}
|
||||
|
||||
resource "google_compute_global_address" "loadbalancer_ip" {
|
||||
|
@ -15,7 +15,7 @@ locals {
|
||||
resource "google_compute_instance_template" "template" {
|
||||
name = local.name
|
||||
machine_type = var.instance_type
|
||||
tags = ["constellation-${var.uid}"]
|
||||
tags = ["constellation-${var.uid}"] // Note that this is also applied as a label
|
||||
labels = merge(var.labels, { constellation-role = local.role_dashed })
|
||||
|
||||
confidential_instance_config {
|
||||
@ -41,8 +41,9 @@ resource "google_compute_instance_template" "template" {
|
||||
}
|
||||
|
||||
metadata = {
|
||||
kube-env = var.kube_env
|
||||
serial-port-enable = var.debug ? "TRUE" : "FALSE"
|
||||
kube-env = var.kube_env
|
||||
constellation-init-secret-hash = var.init_secret_hash
|
||||
serial-port-enable = var.debug ? "TRUE" : "FALSE"
|
||||
}
|
||||
|
||||
network_interface {
|
||||
|
@ -59,6 +59,11 @@ variable "kube_env" {
|
||||
description = "Kubernetes env."
|
||||
}
|
||||
|
||||
variable "init_secret_hash" {
|
||||
type = string
|
||||
description = "Hash of the init secret."
|
||||
}
|
||||
|
||||
variable "named_ports" {
|
||||
type = list(object({ name = string, port = number }))
|
||||
default = []
|
||||
|
@ -1,3 +1,8 @@
|
||||
output "ip" {
|
||||
value = google_compute_global_address.loadbalancer_ip.address
|
||||
}
|
||||
|
||||
output "initSecret" {
|
||||
value = random_password.initSecret.result
|
||||
sensitive = true
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ provider "docker" {
|
||||
}
|
||||
}
|
||||
|
||||
resource "random_password" "initSecret" {
|
||||
length = 32
|
||||
special = true
|
||||
override_special = "_%@"
|
||||
}
|
||||
resource "docker_image" "qemu_metadata" {
|
||||
name = var.metadata_api_image
|
||||
keep_locally = true
|
||||
@ -39,6 +44,8 @@ resource "docker_container" "qemu_metadata" {
|
||||
"${var.name}-network",
|
||||
"--libvirt-uri",
|
||||
"${var.metadata_libvirt_uri}",
|
||||
"--initsecrethash",
|
||||
"${random_password.initSecret.bcrypt_hash}",
|
||||
]
|
||||
mounts {
|
||||
source = abspath(var.libvirt_socket_path)
|
||||
@ -47,6 +54,8 @@ resource "docker_container" "qemu_metadata" {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
module "control_plane" {
|
||||
source = "./modules/instance_group"
|
||||
role = "control-plane"
|
||||
|
@ -1,3 +1,8 @@
|
||||
output "ip" {
|
||||
value = module.control_plane.instance_ips[0]
|
||||
}
|
||||
|
||||
output "initSecret" {
|
||||
value = random_password.initSecret.result
|
||||
sensitive = true
|
||||
}
|
||||
|
@ -109,6 +109,9 @@ func TestCreateCluster(t *testing.T) {
|
||||
"ip": {
|
||||
Value: "192.0.2.100",
|
||||
},
|
||||
"initSecret": {
|
||||
Value: "initSecret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -202,7 +205,7 @@ func TestCreateCluster(t *testing.T) {
|
||||
}
|
||||
|
||||
require.NoError(c.PrepareWorkspace(tc.provider, tc.vars))
|
||||
ip, err := c.CreateCluster(context.Background())
|
||||
ip, initSecret, err := c.CreateCluster(context.Background())
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
@ -210,6 +213,7 @@ func TestCreateCluster(t *testing.T) {
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal("192.0.2.100", ip)
|
||||
assert.Equal("initSecret", initSecret)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ func main() {
|
||||
bindPort := flag.String("port", "8080", "Port to bind to")
|
||||
targetNetwork := flag.String("network", "constellation-network", "Name of the network in QEMU to use")
|
||||
libvirtURI := flag.String("libvirt-uri", "qemu:///system", "URI of the libvirt connection")
|
||||
initSecretHash := flag.String("initsecrethash", "", "brcypt hash of the init secret")
|
||||
flag.Parse()
|
||||
|
||||
log := logger.New(logger.JSONLog, zapcore.InfoLevel)
|
||||
@ -31,7 +32,7 @@ func main() {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
serv := server.New(log, *targetNetwork, &virtwrapper.Connect{Conn: conn})
|
||||
serv := server.New(log, *targetNetwork, *initSecretHash, &virtwrapper.Connect{Conn: conn})
|
||||
if err := serv.ListenAndServe(*bindPort); err != nil {
|
||||
log.With(zap.Error(err)).Fatalf("Failed to serve")
|
||||
}
|
||||
|
@ -24,17 +24,19 @@ import (
|
||||
|
||||
// Server that provides QEMU metadata.
|
||||
type Server struct {
|
||||
log *logger.Logger
|
||||
virt virConnect
|
||||
network string
|
||||
log *logger.Logger
|
||||
virt virConnect
|
||||
network string
|
||||
initSecretHashVal []byte
|
||||
}
|
||||
|
||||
// New creates a new Server.
|
||||
func New(log *logger.Logger, network string, conn virConnect) *Server {
|
||||
func New(log *logger.Logger, network, initSecretHash string, conn virConnect) *Server {
|
||||
return &Server{
|
||||
log: log,
|
||||
virt: conn,
|
||||
network: network,
|
||||
log: log,
|
||||
virt: conn,
|
||||
network: network,
|
||||
initSecretHashVal: []byte(initSecretHash),
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +48,7 @@ func (s *Server) ListenAndServe(port string) error {
|
||||
mux.Handle("/log", http.HandlerFunc(s.postLog))
|
||||
mux.Handle("/pcrs", http.HandlerFunc(s.exportPCRs))
|
||||
mux.Handle("/endpoint", http.HandlerFunc(s.getEndpoint))
|
||||
mux.Handle("/initsecrethash", http.HandlerFunc(s.initSecretHash))
|
||||
|
||||
server := http.Server{
|
||||
Handler: mux,
|
||||
@ -115,6 +118,26 @@ func (s *Server) listPeers(w http.ResponseWriter, r *http.Request) {
|
||||
log.Infof("Request successful")
|
||||
}
|
||||
|
||||
// initSecretHash returns the hash of the init secret.
|
||||
func (s *Server) initSecretHash(w http.ResponseWriter, r *http.Request) {
|
||||
log := s.log.With(zap.String("initSecretHash", r.RemoteAddr))
|
||||
if r.Method != http.MethodGet {
|
||||
log.With(zap.String("method", r.Method)).Errorf("Invalid method for /initSecretHash")
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
log.Infof("Serving GET request for /initsecrethash")
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, err := w.Write(s.initSecretHashVal)
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Errorf("Failed to write init secret hash")
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Infof("Request successful")
|
||||
}
|
||||
|
||||
// getEndpoint returns the IP address of the first control-plane instance.
|
||||
// This allows us to fake a load balancer for QEMU instances.
|
||||
func (s *Server) getEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -72,7 +72,7 @@ func TestListAll(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
server := New(logger.NewTest(t), "test", tc.connect)
|
||||
server := New(logger.NewTest(t), "test", "initSecretHash", tc.connect)
|
||||
|
||||
res, err := server.listAll()
|
||||
|
||||
@ -149,7 +149,7 @@ func TestListSelf(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
server := New(logger.NewTest(t), "test", tc.connect)
|
||||
server := New(logger.NewTest(t), "test", "initSecretHash", tc.connect)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://192.0.0.1/self", nil)
|
||||
require.NoError(err)
|
||||
@ -211,7 +211,7 @@ func TestListPeers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
server := New(logger.NewTest(t), "test", tc.connect)
|
||||
server := New(logger.NewTest(t), "test", "initSecretHash", tc.connect)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://192.0.0.1/peers", nil)
|
||||
require.NoError(err)
|
||||
@ -266,7 +266,7 @@ func TestPostLog(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
server := New(logger.NewTest(t), "test", &stubConnect{})
|
||||
server := New(logger.NewTest(t), "test", "initSecretHash", &stubConnect{})
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), tc.method, "http://192.0.0.1/logs", tc.message)
|
||||
require.NoError(err)
|
||||
@ -346,7 +346,7 @@ func TestExportPCRs(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
server := New(logger.NewTest(t), "test", tc.connect)
|
||||
server := New(logger.NewTest(t), "test", "initSecretHash", tc.connect)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), tc.method, "http://192.0.0.1/pcrs", strings.NewReader(tc.message))
|
||||
require.NoError(err)
|
||||
@ -365,6 +365,58 @@ func TestExportPCRs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitSecretHash(t *testing.T) {
|
||||
defaultConnect := &stubConnect{
|
||||
network: stubNetwork{
|
||||
leases: []libvirt.NetworkDHCPLease{
|
||||
{
|
||||
IPaddr: "192.0.100.1",
|
||||
Hostname: "control-plane-0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
testCases := map[string]struct {
|
||||
connect *stubConnect
|
||||
method string
|
||||
wantHash string
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
connect: defaultConnect,
|
||||
method: http.MethodGet,
|
||||
},
|
||||
"wrong method": {
|
||||
connect: defaultConnect,
|
||||
method: http.MethodPost,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
server := New(logger.NewTest(t), "test", tc.wantHash, defaultConnect)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), tc.method, "http://192.0.0.1/initsecrethash", nil)
|
||||
require.NoError(err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
server.initSecretHash(w, req)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.NotEqual(http.StatusOK, w.Code)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(http.StatusOK, w.Code)
|
||||
assert.Equal(tc.wantHash, w.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustMarshal(t *testing.T, v any) string {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
|
@ -109,6 +109,15 @@ func (c *Cloud) UID(ctx context.Context) (string, error) {
|
||||
return readInstanceTag(ctx, c.imds, cloud.TagUID)
|
||||
}
|
||||
|
||||
// InitSecretHash returns the InitSecretHash of the current instance.
|
||||
func (c *Cloud) InitSecretHash(ctx context.Context) ([]byte, error) {
|
||||
initSecretHash, err := readInstanceTag(ctx, c.imds, cloud.TagInitSecretHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving init secret hash tag: %w", err)
|
||||
}
|
||||
return []byte(initSecretHash), nil
|
||||
}
|
||||
|
||||
// GetLoadBalancerEndpoint returns the endpoint of the load balancer.
|
||||
func (c *Cloud) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
uid, err := readInstanceTag(ctx, c.imds, cloud.TagUID)
|
||||
|
@ -67,7 +67,8 @@ func TestSelf(t *testing.T) {
|
||||
},
|
||||
},
|
||||
tags: map[string]string{
|
||||
cloud.TagRole: "worker",
|
||||
cloud.TagRole: "worker",
|
||||
cloud.TagInitSecretHash: "initSecretHash",
|
||||
},
|
||||
},
|
||||
wantSelf: metadata.InstanceMetadata{
|
||||
|
@ -250,6 +250,15 @@ func (c *Cloud) UID(ctx context.Context) (string, error) {
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
// InitSecretHash retrieves the InitSecretHash of the current instance.
|
||||
func (c *Cloud) InitSecretHash(ctx context.Context) ([]byte, error) {
|
||||
initSecretHash, err := c.imds.initSecretHash(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving init secret hash: %w", err)
|
||||
}
|
||||
return []byte(initSecretHash), nil
|
||||
}
|
||||
|
||||
// getLoadBalancer retrieves a load balancer from cloud provider metadata.
|
||||
func (c *Cloud) getLoadBalancer(ctx context.Context, resourceGroup, uid string) (*armnetwork.LoadBalancer, error) {
|
||||
pager := c.loadBalancerAPI.NewListPager(resourceGroup, nil)
|
||||
@ -283,8 +292,12 @@ func (c *Cloud) getInstance(ctx context.Context, providerID string) (metadata.In
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("retrieving VM network interfaces: %w", err)
|
||||
}
|
||||
instance, err := convertToInstanceMetadata(vmResp.VirtualMachineScaleSetVM, networkInterfaces)
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("converting VM to instance metadata: %w", err)
|
||||
}
|
||||
|
||||
return convertToInstanceMetadata(vmResp.VirtualMachineScaleSetVM, networkInterfaces)
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// getNetworkSecurityGroupName returns the security group name of the resource group.
|
||||
|
@ -360,6 +360,7 @@ func TestGetInstance(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
scaleSetsVMAPI *stubVirtualMachineScaleSetVMsAPI
|
||||
networkInterfacesAPI *stubNetworkInterfacesAPI
|
||||
IMDSAPI *stubIMDSAPI
|
||||
providerID string
|
||||
wantInstance metadata.InstanceMetadata
|
||||
wantErr bool
|
||||
@ -368,6 +369,7 @@ func TestGetInstance(t *testing.T) {
|
||||
scaleSetsVMAPI: successVMAPI,
|
||||
networkInterfacesAPI: successNetworkAPI,
|
||||
providerID: sampleProviderID,
|
||||
IMDSAPI: &stubIMDSAPI{},
|
||||
wantInstance: metadata.InstanceMetadata{
|
||||
Name: "scale-set-name-instance-id",
|
||||
ProviderID: sampleProviderID,
|
||||
@ -397,6 +399,7 @@ func TestGetInstance(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
IMDSAPI: &stubIMDSAPI{},
|
||||
networkInterfacesAPI: successNetworkAPI,
|
||||
providerID: sampleProviderID,
|
||||
wantInstance: metadata.InstanceMetadata{
|
||||
@ -408,12 +411,14 @@ func TestGetInstance(t *testing.T) {
|
||||
},
|
||||
"invalid provider ID": {
|
||||
scaleSetsVMAPI: successVMAPI,
|
||||
IMDSAPI: &stubIMDSAPI{},
|
||||
networkInterfacesAPI: successNetworkAPI,
|
||||
providerID: "invalid",
|
||||
wantErr: true,
|
||||
},
|
||||
"vm API error": {
|
||||
scaleSetsVMAPI: &stubVirtualMachineScaleSetVMsAPI{getErr: someErr},
|
||||
IMDSAPI: &stubIMDSAPI{},
|
||||
networkInterfacesAPI: successNetworkAPI,
|
||||
providerID: sampleProviderID,
|
||||
wantErr: true,
|
||||
@ -421,6 +426,7 @@ func TestGetInstance(t *testing.T) {
|
||||
"network API error": {
|
||||
scaleSetsVMAPI: successVMAPI,
|
||||
networkInterfacesAPI: &stubNetworkInterfacesAPI{getErr: someErr},
|
||||
IMDSAPI: &stubIMDSAPI{},
|
||||
providerID: sampleProviderID,
|
||||
wantErr: true,
|
||||
},
|
||||
@ -431,6 +437,7 @@ func TestGetInstance(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
metadata := Cloud{
|
||||
imds: tc.IMDSAPI,
|
||||
scaleSetsVMAPI: tc.scaleSetsVMAPI,
|
||||
netIfacAPI: tc.networkInterfacesAPI,
|
||||
}
|
||||
@ -481,6 +488,42 @@ func TestUID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitSecretHash(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
imdsAPI *stubIMDSAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
imdsAPI: &stubIMDSAPI{
|
||||
initSecretHashVal: "initSecretHash",
|
||||
},
|
||||
},
|
||||
"error": {
|
||||
imdsAPI: &stubIMDSAPI{
|
||||
initSecretHashErr: errors.New("failed"),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
imds: tc.imdsAPI,
|
||||
}
|
||||
initSecretHash, err := cloud.InitSecretHash(context.Background())
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal([]byte(tc.imdsAPI.initSecretHashVal), initSecretHash)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
networkIfaceResponse := &stubNetworkInterfacesAPI{
|
||||
@ -978,6 +1021,8 @@ type stubIMDSAPI struct {
|
||||
uidVal string
|
||||
nameErr error
|
||||
nameVal string
|
||||
initSecretHashVal string
|
||||
initSecretHashErr error
|
||||
}
|
||||
|
||||
func (a *stubIMDSAPI) providerID(ctx context.Context) (string, error) {
|
||||
@ -1000,6 +1045,10 @@ func (a *stubIMDSAPI) name(ctx context.Context) (string, error) {
|
||||
return a.nameVal, a.nameErr
|
||||
}
|
||||
|
||||
func (a *stubIMDSAPI) initSecretHash(ctx context.Context) (string, error) {
|
||||
return a.initSecretHashVal, a.initSecretHashErr
|
||||
}
|
||||
|
||||
type stubVirtualMachineScaleSetVMPager struct {
|
||||
list []armcomputev2.VirtualMachineScaleSetVM
|
||||
fetchErr error
|
||||
|
@ -114,6 +114,24 @@ func (c *imdsClient) uid(ctx context.Context) (string, error) {
|
||||
return "", fmt.Errorf("unable to get uid from metadata tags %v", c.cache.Compute.Tags)
|
||||
}
|
||||
|
||||
// initSecretHash returns the hash of the init secret of the cluster, based on the tags on the instance
|
||||
// the function is called from, which are inherited from the scale set.
|
||||
func (c *imdsClient) initSecretHash(ctx context.Context) (string, error) {
|
||||
if c.timeForUpdate() || len(c.cache.Compute.Tags) == 0 {
|
||||
if err := c.update(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
for _, tag := range c.cache.Compute.Tags {
|
||||
if tag.Name == cloud.TagInitSecretHash {
|
||||
return tag.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unable to get tag %s from metadata tags %v", cloud.TagInitSecretHash, c.cache.Compute.Tags)
|
||||
}
|
||||
|
||||
// role returns the role of the instance the function is called from.
|
||||
func (c *imdsClient) role(ctx context.Context) (role.Role, error) {
|
||||
if c.timeForUpdate() || len(c.cache.Compute.Tags) == 0 {
|
||||
|
@ -21,6 +21,7 @@ type imdsAPI interface {
|
||||
resourceGroup(ctx context.Context) (string, error)
|
||||
subscriptionID(ctx context.Context) (string, error)
|
||||
uid(ctx context.Context) (string, error)
|
||||
initSecretHash(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
type virtualNetworksAPI interface {
|
||||
|
@ -11,4 +11,6 @@ const (
|
||||
TagRole = "constellation-role"
|
||||
// TagUID is the tag/label key used to identify the UID of a cluster.
|
||||
TagUID = "constellation-uid"
|
||||
// TagInitSecretHash is the tag/label key used to identify the hash of the init secret.
|
||||
TagInitSecretHash = "constellation-init-secret-hash"
|
||||
)
|
||||
|
@ -181,6 +181,19 @@ func (c *Cloud) UID(ctx context.Context) (string, error) {
|
||||
return c.uid(ctx, project, zone, instanceName)
|
||||
}
|
||||
|
||||
// InitSecretHash retrieves the InitSecretHash of the current instance.
|
||||
func (c *Cloud) InitSecretHash(ctx context.Context) ([]byte, error) {
|
||||
project, zone, instanceName, err := c.retrieveInstanceInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initSecretHash, err := c.initSecretHash(ctx, project, zone, instanceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving init secret hash: %w", err)
|
||||
}
|
||||
return []byte(initSecretHash), nil
|
||||
}
|
||||
|
||||
// getInstance retrieves an instance using its project, zone and name, and parses it to metadata.InstanceMetadata.
|
||||
func (c *Cloud) getInstance(ctx context.Context, project, zone, instanceName string) (metadata.InstanceMetadata, error) {
|
||||
gcpInstance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
|
||||
@ -205,7 +218,9 @@ func (c *Cloud) getInstance(ctx context.Context, project, zone, instanceName str
|
||||
if err != nil {
|
||||
return metadata.InstanceMetadata{}, fmt.Errorf("converting instance: %w", err)
|
||||
}
|
||||
|
||||
instance.SecondaryIPRange = subnetCIDR
|
||||
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
@ -268,7 +283,39 @@ func (c *Cloud) uid(ctx context.Context, project, zone, instanceName string) (st
|
||||
if instance == nil || instance.Labels == nil {
|
||||
return "", errors.New("retrieving compute instance: received instance with invalid labels")
|
||||
}
|
||||
return instance.Labels[cloud.TagUID], nil
|
||||
uid, ok := instance.Labels[cloud.TagUID]
|
||||
if !ok {
|
||||
return "", errors.New("retrieving compute instance: received instance with no UID label")
|
||||
}
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
// initSecretHash retrieves the init secret hash of the instance identified by project, zone and instanceName.
|
||||
// The init secret hash is retrieved from the instance's labels.
|
||||
func (c *Cloud) initSecretHash(ctx context.Context, project, zone, instanceName string) (string, error) {
|
||||
instance, err := c.instanceAPI.Get(ctx, &computepb.GetInstanceRequest{
|
||||
Project: project,
|
||||
Zone: zone,
|
||||
Instance: instanceName,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("retrieving compute instance: %w", err)
|
||||
}
|
||||
if instance == nil || instance.Metadata == nil {
|
||||
return "", errors.New("retrieving compute instance: received instance with invalid metadata")
|
||||
}
|
||||
if len(instance.Metadata.Items) == 0 {
|
||||
return "", errors.New("retrieving compute instance: received instance with empty metadata")
|
||||
}
|
||||
for _, item := range instance.Metadata.Items {
|
||||
if item == nil || item.Key == nil || item.Value == nil {
|
||||
return "", errors.New("retrieving compute instance: received instance with invalid metadata item")
|
||||
}
|
||||
if *item.Key == cloud.TagInitSecretHash {
|
||||
return *item.Value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("retrieving compute instance: received instance with no init secret hash label")
|
||||
}
|
||||
|
||||
// convertToInstanceMetadata converts a *computepb.Instance to a metadata.InstanceMetadata.
|
||||
|
@ -36,8 +36,17 @@ func TestGetInstance(t *testing.T) {
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
cloud.TagUID: "1234",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
cloud.TagInitSecretHash: "initSecretHash",
|
||||
},
|
||||
Metadata: &computepb.Metadata{
|
||||
Items: []*computepb.Items{
|
||||
{
|
||||
Key: proto.String(cloud.TagInitSecretHash),
|
||||
Value: proto.String("initSecretHash"),
|
||||
},
|
||||
},
|
||||
},
|
||||
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||
{
|
||||
@ -748,6 +757,110 @@ func TestUID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitSecretHash(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
imds stubIMDS
|
||||
instanceAPI stubInstanceAPI
|
||||
wantInitSecretHash string
|
||||
wantErr bool
|
||||
}{
|
||||
"success": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
Metadata: &computepb.Metadata{
|
||||
Items: []*computepb.Items{
|
||||
{
|
||||
Key: proto.String(cloud.TagInitSecretHash),
|
||||
Value: proto.String("initSecretHash"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantInitSecretHash: "initSecretHash",
|
||||
},
|
||||
"imds error": {
|
||||
imds: stubIMDS{
|
||||
projectIDErr: someErr,
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: &computepb.Instance{
|
||||
Name: proto.String("someInstance"),
|
||||
Zone: proto.String("someZone-west3-b"),
|
||||
Labels: map[string]string{
|
||||
cloud.TagInitSecretHash: "initSecretHash",
|
||||
cloud.TagRole: role.ControlPlane.String(),
|
||||
},
|
||||
Metadata: &computepb.Metadata{
|
||||
Items: []*computepb.Items{
|
||||
{
|
||||
Key: proto.String(cloud.TagInitSecretHash),
|
||||
Value: proto.String("initSecretHash"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"instance error": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instanceErr: someErr,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"invalid instance": {
|
||||
imds: stubIMDS{
|
||||
projectID: "someProject",
|
||||
zone: "someZone-west3-b",
|
||||
instanceName: "someInstance",
|
||||
},
|
||||
instanceAPI: stubInstanceAPI{
|
||||
instance: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cloud := &Cloud{
|
||||
imds: &tc.imds,
|
||||
instanceAPI: &tc.instanceAPI,
|
||||
}
|
||||
|
||||
initSecretHash, err := cloud.InitSecretHash(context.Background())
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Equal([]byte(tc.wantInitSecretHash), initSecretHash)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stubForwardingRulesAPI struct {
|
||||
iterator forwardingRuleIterator
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ package qemu
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -62,6 +63,15 @@ func (c *Cloud) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||
return endpoint, err
|
||||
}
|
||||
|
||||
// InitSecretHash returns the hash of the init secret.
|
||||
func (c *Cloud) InitSecretHash(ctx context.Context) ([]byte, error) {
|
||||
initSecretHash, err := c.retrieveMetadata(ctx, "/initsecrethash")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not retrieve init secret hash: %w", err)
|
||||
}
|
||||
return initSecretHash, nil
|
||||
}
|
||||
|
||||
// UID returns the UID of the constellation.
|
||||
func (c *Cloud) UID(ctx context.Context) (string, error) {
|
||||
// We expect only one constellation to be deployed in the same QEMU / libvirt environment.
|
||||
|
Loading…
Reference in New Issue
Block a user