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