mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
Manually manage GCP service accounts
This commit is contained in:
parent
f9c70d5c5a
commit
e761c9bf97
@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Create multiple load balancers to enable load balacing TCP traffic for different backend services. All load balancers currently share the same public IP address.
|
||||
- Improve rollback on GCP resource termination. You can now terminate multiple times.
|
||||
- Implement SSH peer to peer distribution between debugd nodes.
|
||||
- GCP service account can now be managed manually.
|
||||
|
||||
### Changed
|
||||
<!-- For changes in existing functionality. -->
|
||||
|
@ -15,12 +15,10 @@ type gcpclient interface {
|
||||
CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error
|
||||
CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error
|
||||
CreateLoadBalancers(ctx context.Context) error
|
||||
CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error)
|
||||
TerminateFirewall(ctx context.Context) error
|
||||
TerminateVPCs(context.Context) error
|
||||
TerminateLoadBalancers(context.Context) error
|
||||
TerminateInstances(context.Context) error
|
||||
TerminateServiceAccount(ctx context.Context) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/edgelesssys/constellation/internal/azureshared"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
||||
"github.com/edgelesssys/constellation/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
@ -244,7 +243,6 @@ type fakeGcpClient struct {
|
||||
uid string
|
||||
name string
|
||||
zone string
|
||||
serviceAccount string
|
||||
loadbalancers []string
|
||||
}
|
||||
|
||||
@ -264,7 +262,6 @@ func (c *fakeGcpClient) GetState() state.ConstellationState {
|
||||
Name: c.name,
|
||||
UID: c.uid,
|
||||
GCPZone: c.zone,
|
||||
GCPServiceAccount: c.serviceAccount,
|
||||
GCPLoadbalancers: c.loadbalancers,
|
||||
}
|
||||
}
|
||||
@ -283,7 +280,6 @@ func (c *fakeGcpClient) SetState(stat state.ConstellationState) {
|
||||
c.name = stat.Name
|
||||
c.uid = stat.UID
|
||||
c.zone = stat.GCPZone
|
||||
c.serviceAccount = stat.GCPServiceAccount
|
||||
c.loadbalancers = stat.GCPLoadbalancers
|
||||
}
|
||||
|
||||
@ -321,22 +317,6 @@ func (c *fakeGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error) {
|
||||
c.serviceAccount = "service-account@" + c.project + ".iam.gserviceaccount.com"
|
||||
return gcpshared.ServiceAccountKey{
|
||||
Type: "service_account",
|
||||
ProjectID: c.project,
|
||||
PrivateKeyID: "key-id",
|
||||
PrivateKey: "-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n",
|
||||
ClientEmail: c.serviceAccount,
|
||||
ClientID: "client-id",
|
||||
AuthURI: "https://accounts.google.com/o/oauth2/auth",
|
||||
TokenURI: "https://accounts.google.com/o/oauth2/token",
|
||||
AuthProviderX509CertURL: "https://www.googleapis.com/oauth2/v1/certs",
|
||||
ClientX509CertURL: "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email",
|
||||
}.ToCloudServiceAccountURI(), nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) CreateLoadBalancers(ctx context.Context) error {
|
||||
c.loadbalancers = []string{"kube-lb", "boot-lb", "verify-lb"}
|
||||
return nil
|
||||
@ -369,11 +349,6 @@ func (c *fakeGcpClient) TerminateInstances(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) TerminateServiceAccount(context.Context) error {
|
||||
c.serviceAccount = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) TerminateLoadBalancers(context.Context) error {
|
||||
c.loadbalancers = nil
|
||||
return nil
|
||||
@ -384,23 +359,20 @@ func (c *fakeGcpClient) Close() error {
|
||||
}
|
||||
|
||||
type stubGcpClient struct {
|
||||
terminateFirewallCalled bool
|
||||
terminateInstancesCalled bool
|
||||
terminateVPCsCalled bool
|
||||
terminateServiceAccountCalled bool
|
||||
closeCalled bool
|
||||
terminateFirewallCalled bool
|
||||
terminateInstancesCalled bool
|
||||
terminateVPCsCalled bool
|
||||
closeCalled bool
|
||||
|
||||
createVPCsErr error
|
||||
createFirewallErr error
|
||||
createInstancesErr error
|
||||
createServiceAccountErr error
|
||||
createLoadBalancerErr error
|
||||
terminateFirewallErr error
|
||||
terminateVPCsErr error
|
||||
terminateInstancesErr error
|
||||
terminateServiceAccountErr error
|
||||
terminateLoadBalancerErr error
|
||||
closeErr error
|
||||
createVPCsErr error
|
||||
createFirewallErr error
|
||||
createInstancesErr error
|
||||
createLoadBalancerErr error
|
||||
terminateFirewallErr error
|
||||
terminateVPCsErr error
|
||||
terminateInstancesErr error
|
||||
terminateLoadBalancerErr error
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) GetState() state.ConstellationState {
|
||||
@ -422,10 +394,6 @@ func (c *stubGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateI
|
||||
return c.createInstancesErr
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error) {
|
||||
return gcpshared.ServiceAccountKey{}.ToCloudServiceAccountURI(), c.createServiceAccountErr
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) CreateLoadBalancers(ctx context.Context) error {
|
||||
return c.createLoadBalancerErr
|
||||
}
|
||||
@ -445,11 +413,6 @@ func (c *stubGcpClient) TerminateInstances(context.Context) error {
|
||||
return c.terminateInstancesErr
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) TerminateServiceAccount(context.Context) error {
|
||||
c.terminateServiceAccountCalled = true
|
||||
return c.terminateServiceAccountErr
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) TerminateLoadBalancers(context.Context) error {
|
||||
return c.terminateLoadBalancerErr
|
||||
}
|
||||
|
@ -34,18 +34,7 @@ func (c *ServiceAccountCreator) Create(ctx context.Context, stat state.Constella
|
||||
provider := cloudprovider.FromString(stat.CloudProvider)
|
||||
switch provider {
|
||||
case cloudprovider.GCP:
|
||||
cl, err := c.newGCPClient(ctx)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, err
|
||||
}
|
||||
defer cl.Close()
|
||||
|
||||
serviceAccount, stat, err := c.createServiceAccountGCP(ctx, cl, stat, config)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
return serviceAccount, stat, err
|
||||
return "", state.ConstellationState{}, fmt.Errorf("creating service account not supported for GCP")
|
||||
case cloudprovider.Azure:
|
||||
cl, err := c.newAzureClient(stat.AzureSubscription, stat.AzureTenant)
|
||||
if err != nil {
|
||||
@ -65,22 +54,6 @@ func (c *ServiceAccountCreator) Create(ctx context.Context, stat state.Constella
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ServiceAccountCreator) createServiceAccountGCP(ctx context.Context, cl gcpclient,
|
||||
stat state.ConstellationState, config *config.Config,
|
||||
) (string, state.ConstellationState, error) {
|
||||
cl.SetState(stat)
|
||||
|
||||
input := gcpcl.ServiceAccountInput{
|
||||
Roles: config.Provider.GCP.ServiceAccountRoles,
|
||||
}
|
||||
serviceAccount, err := cl.CreateServiceAccount(ctx, input)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, fmt.Errorf("creating service account: %w", err)
|
||||
}
|
||||
|
||||
return serviceAccount, cl.GetState(), nil
|
||||
}
|
||||
|
||||
func (c *ServiceAccountCreator) createServiceAccountAzure(ctx context.Context, cl azureclient,
|
||||
stat state.ConstellationState, _ *config.Config,
|
||||
) (string, state.ConstellationState, error) {
|
||||
|
@ -6,27 +6,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestServiceAccountCreator(t *testing.T) {
|
||||
someGCPState := func() state.ConstellationState {
|
||||
return state.ConstellationState{
|
||||
CloudProvider: cloudprovider.GCP.String(),
|
||||
GCPProject: "project",
|
||||
GCPWorkerInstances: cloudtypes.Instances{},
|
||||
GCPControlPlaneInstances: cloudtypes.Instances{},
|
||||
GCPWorkerInstanceGroup: "workers-group",
|
||||
GCPControlPlaneInstanceGroup: "controlplane-group",
|
||||
GCPWorkerInstanceTemplate: "template",
|
||||
GCPControlPlaneInstanceTemplate: "template",
|
||||
GCPNetwork: "network",
|
||||
GCPFirewalls: []string{},
|
||||
}
|
||||
}
|
||||
someAzureState := func() state.ConstellationState {
|
||||
return state.ConstellationState{
|
||||
CloudProvider: cloudprovider.Azure.String(),
|
||||
@ -42,32 +27,6 @@ func TestServiceAccountCreator(t *testing.T) {
|
||||
wantErr bool
|
||||
wantStateMutator func(*state.ConstellationState)
|
||||
}{
|
||||
"gcp": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return &fakeGcpClient{}, nil
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantStateMutator: func(stat *state.ConstellationState) {
|
||||
stat.GCPServiceAccount = "service-account@project.iam.gserviceaccount.com"
|
||||
},
|
||||
},
|
||||
"gcp newGCPClient error": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return nil, someErr
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp client createServiceAccount error": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return &stubGcpClient{createServiceAccountErr: someErr}, nil
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure": {
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return &fakeAzureClient{}, nil
|
||||
|
@ -65,7 +65,8 @@ func (t *Terminator) terminateGCP(ctx context.Context, cl gcpclient, state state
|
||||
if err := cl.TerminateVPCs(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return cl.TerminateServiceAccount(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error {
|
||||
|
@ -29,7 +29,6 @@ func TestTerminator(t *testing.T) {
|
||||
GCPControlPlaneInstanceTemplate: "template",
|
||||
GCPNetwork: "network",
|
||||
GCPFirewalls: []string{"a", "b", "c"},
|
||||
GCPServiceAccount: "service-account@project.iam.gserviceaccount.com",
|
||||
}
|
||||
}
|
||||
someAzureState := func() state.ConstellationState {
|
||||
@ -80,11 +79,6 @@ func TestTerminator(t *testing.T) {
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp terminateServiceAccount error": {
|
||||
gcpclient: &stubGcpClient{terminateServiceAccountErr: someErr},
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure": {
|
||||
azureclient: &stubAzureClient{},
|
||||
state: someAzureState(),
|
||||
@ -135,7 +129,6 @@ func TestTerminator(t *testing.T) {
|
||||
assert.True(cl.terminateFirewallCalled)
|
||||
assert.True(cl.terminateInstancesCalled)
|
||||
assert.True(cl.terminateVPCsCalled)
|
||||
assert.True(cl.terminateServiceAccountCalled)
|
||||
assert.True(cl.closeCalled)
|
||||
case cloudprovider.Azure:
|
||||
cl := tc.azureclient.(*stubAzureClient)
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/internal/crypto"
|
||||
"github.com/edgelesssys/constellation/internal/deploy/ssh"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/dialer"
|
||||
grpcRetry "github.com/edgelesssys/constellation/internal/grpc/retry"
|
||||
"github.com/edgelesssys/constellation/internal/license"
|
||||
@ -116,13 +117,22 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println("Creating service account ...")
|
||||
serviceAccount, stat, err := serviceAccCreator.Create(cmd.Context(), stat, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileHandler.WriteJSON(constants.StateFilename, stat, file.OptOverwrite); err != nil {
|
||||
return err
|
||||
var serviceAccURI string
|
||||
// Temporary legacy flow for Azure.
|
||||
if provider == cloudprovider.Azure {
|
||||
cmd.Println("Creating service account ...")
|
||||
serviceAccURI, stat, err = serviceAccCreator.Create(cmd.Context(), stat, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fileHandler.WriteJSON(constants.StateFilename, stat, file.OptOverwrite); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
serviceAccURI, err = getMarschaledServiceAccountURI(provider, config, fileHandler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
workers, err := getScalingGroupsFromState(stat, config)
|
||||
@ -150,7 +160,7 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
|
||||
StorageUri: kms.NoStoreURI,
|
||||
KeyEncryptionKeyId: "",
|
||||
UseExistingKek: false,
|
||||
CloudServiceAccountUri: serviceAccount,
|
||||
CloudServiceAccountUri: serviceAccURI,
|
||||
KubernetesVersion: config.KubernetesVersion,
|
||||
SshUserKeys: ssh.ToProtoSlice(sshUsers),
|
||||
HelmDeployments: helmDeployments,
|
||||
@ -352,6 +362,29 @@ func readIPFromIDFile(fileHandler file.Handler) (string, error) {
|
||||
return idFile.IP, nil
|
||||
}
|
||||
|
||||
func getMarschaledServiceAccountURI(provider cloudprovider.Provider, config *config.Config, fileHandler file.Handler) (string, error) {
|
||||
switch provider {
|
||||
case cloudprovider.GCP:
|
||||
path := config.Provider.GCP.ServiceAccountKeyPath
|
||||
|
||||
var key gcpshared.ServiceAccountKey
|
||||
if err := fileHandler.ReadJSON(path, &key); err != nil {
|
||||
return "", fmt.Errorf("reading service account key from path %q: %w", path, err)
|
||||
}
|
||||
|
||||
return key.ToCloudServiceAccountURI(), nil
|
||||
|
||||
case cloudprovider.Azure:
|
||||
return "", fmt.Errorf("TODO")
|
||||
|
||||
case cloudprovider.QEMU:
|
||||
return "", nil // QEMU does not use service account keys
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported cloud provider %q", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func getScalingGroupsFromState(stat state.ConstellationState, config *config.Config) (workers cloudtypes.ScalingGroup, err error) {
|
||||
switch cloudprovider.FromString(stat.CloudProvider) {
|
||||
case cloudprovider.GCP:
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/gcpshared"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/atlscredentials"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/dialer"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/testdialer"
|
||||
@ -43,16 +44,19 @@ func TestInitArgumentValidation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInitialize(t *testing.T) {
|
||||
testGcpState := state.ConstellationState{
|
||||
testGcpState := &state.ConstellationState{
|
||||
CloudProvider: "GCP",
|
||||
GCPWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
|
||||
}
|
||||
testAzureState := state.ConstellationState{
|
||||
gcpServiceAccKey := &gcpshared.ServiceAccountKey{
|
||||
Type: "service_account",
|
||||
}
|
||||
testAzureState := &state.ConstellationState{
|
||||
CloudProvider: "Azure",
|
||||
AzureWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
|
||||
AzureResourceGroup: "test",
|
||||
}
|
||||
testQemuState := state.ConstellationState{
|
||||
testQemuState := &state.ConstellationState{
|
||||
CloudProvider: "QEMU",
|
||||
QEMUWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
|
||||
}
|
||||
@ -61,77 +65,86 @@ func TestInitialize(t *testing.T) {
|
||||
OwnerId: []byte("ownerID"),
|
||||
ClusterId: []byte("clusterID"),
|
||||
}
|
||||
serviceAccPath := "/test/service-account.json"
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
existingState state.ConstellationState
|
||||
existingIDFile clusterIDsFile
|
||||
serviceAccountCreator stubServiceAccountCreator
|
||||
helmLoader stubHelmLoader
|
||||
initServerAPI *stubInitServer
|
||||
endpointFlag string
|
||||
setAutoscaleFlag bool
|
||||
wantErr bool
|
||||
state *state.ConstellationState
|
||||
existingIDFile *clusterIDsFile
|
||||
serviceAccCreator serviceAccountCreator
|
||||
configMutator func(*config.Config)
|
||||
serviceAccKey *gcpshared.ServiceAccountKey
|
||||
helmLoader stubHelmLoader
|
||||
initServerAPI *stubInitServer
|
||||
endpointFlag string
|
||||
setAutoscaleFlag bool
|
||||
wantErr bool
|
||||
}{
|
||||
"initialize some gcp instances": {
|
||||
existingState: testGcpState,
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
},
|
||||
"initialize some azure instances": {
|
||||
existingState: testAzureState,
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
state: testGcpState,
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
|
||||
serviceAccKey: gcpServiceAccKey,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
},
|
||||
"initialize some azure instances": {
|
||||
state: testAzureState,
|
||||
serviceAccCreator: &stubServiceAccountCreator{},
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
},
|
||||
"initialize some qemu instances": {
|
||||
existingState: testQemuState,
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
state: testQemuState,
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
},
|
||||
"initialize gcp with autoscaling": {
|
||||
existingState: testGcpState,
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
state: testGcpState,
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
|
||||
serviceAccKey: gcpServiceAccKey,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
setAutoscaleFlag: true,
|
||||
},
|
||||
"initialize azure with autoscaling": {
|
||||
existingState: testAzureState,
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
setAutoscaleFlag: true,
|
||||
state: testAzureState,
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
serviceAccCreator: &stubServiceAccountCreator{},
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
setAutoscaleFlag: true,
|
||||
},
|
||||
"initialize with endpoint flag": {
|
||||
existingState: testGcpState,
|
||||
state: testGcpState,
|
||||
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
|
||||
serviceAccKey: gcpServiceAccKey,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
endpointFlag: "192.0.2.1",
|
||||
},
|
||||
"empty state": {
|
||||
existingState: state.ConstellationState{},
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
state: &state.ConstellationState{},
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
initServerAPI: &stubInitServer{},
|
||||
wantErr: true,
|
||||
},
|
||||
"neither endpoint flag nor id file": {
|
||||
existingState: state.ConstellationState{},
|
||||
initServerAPI: &stubInitServer{},
|
||||
wantErr: true,
|
||||
state: &state.ConstellationState{},
|
||||
wantErr: true,
|
||||
},
|
||||
"init call fails": {
|
||||
existingState: testGcpState,
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
state: testGcpState,
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
initServerAPI: &stubInitServer{initErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail to create service account": {
|
||||
existingState: testGcpState,
|
||||
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
|
||||
initServerAPI: &stubInitServer{},
|
||||
serviceAccountCreator: stubServiceAccountCreator{createErr: someErr},
|
||||
wantErr: true,
|
||||
state: testAzureState,
|
||||
existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
|
||||
initServerAPI: &stubInitServer{},
|
||||
serviceAccCreator: &stubServiceAccountCreator{createErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail to load helm charts": {
|
||||
existingState: testGcpState,
|
||||
state: testGcpState,
|
||||
helmLoader: stubHelmLoader{loadErr: someErr},
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
wantErr: true,
|
||||
@ -143,6 +156,7 @@ func TestInitialize(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// Networking
|
||||
netDialer := testdialer.NewBufconnDialer()
|
||||
newDialer := func(*cloudcmd.Validator) *dialer.Dialer {
|
||||
return dialer.New(nil, nil, netDialer)
|
||||
@ -155,30 +169,44 @@ func TestInitialize(t *testing.T) {
|
||||
go initServer.Serve(listener)
|
||||
defer initServer.GracefulStop()
|
||||
|
||||
// Command
|
||||
cmd := NewInitCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
var errOut bytes.Buffer
|
||||
cmd.SetErr(&errOut)
|
||||
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
|
||||
fs := afero.NewMemMapFs()
|
||||
fileHandler := file.NewHandler(fs)
|
||||
|
||||
config := defaultConfigWithExpectedMeasurements(t, cloudprovider.FromString(tc.existingState.CloudProvider))
|
||||
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config))
|
||||
require.NoError(fileHandler.WriteJSON(constants.StateFilename, tc.existingState, file.OptNone))
|
||||
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.existingIDFile, file.OptNone))
|
||||
// Flags
|
||||
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
|
||||
require.NoError(cmd.Flags().Set("autoscale", strconv.FormatBool(tc.setAutoscaleFlag)))
|
||||
if tc.endpointFlag != "" {
|
||||
require.NoError(cmd.Flags().Set("endpoint", tc.endpointFlag))
|
||||
}
|
||||
|
||||
// File system preparation
|
||||
fs := afero.NewMemMapFs()
|
||||
fileHandler := file.NewHandler(fs)
|
||||
config := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.FromString(tc.state.CloudProvider))
|
||||
if tc.configMutator != nil {
|
||||
tc.configMutator(config)
|
||||
}
|
||||
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptNone))
|
||||
if tc.state != nil {
|
||||
require.NoError(fileHandler.WriteJSON(constants.StateFilename, tc.state, file.OptNone))
|
||||
}
|
||||
if tc.existingIDFile != nil {
|
||||
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.existingIDFile, file.OptNone))
|
||||
}
|
||||
if tc.serviceAccKey != nil {
|
||||
require.NoError(fileHandler.WriteJSON(serviceAccPath, tc.serviceAccKey, file.OptNone))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
|
||||
defer cancel()
|
||||
cmd.SetContext(ctx)
|
||||
|
||||
err := initialize(cmd, newDialer, &tc.serviceAccountCreator, fileHandler, &tc.helmLoader, &stubLicenseClient{})
|
||||
err := initialize(cmd, newDialer, tc.serviceAccCreator, fileHandler, &tc.helmLoader, &stubLicenseClient{})
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
@ -510,28 +538,30 @@ func (s *stubInitServer) Init(ctx context.Context, req *initproto.InitRequest) (
|
||||
return s.initResp, s.initErr
|
||||
}
|
||||
|
||||
func defaultConfigWithExpectedMeasurements(t *testing.T, csp cloudprovider.Provider) *config.Config {
|
||||
func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, csp cloudprovider.Provider) *config.Config {
|
||||
t.Helper()
|
||||
config := config.Default()
|
||||
|
||||
config.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
|
||||
config.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
|
||||
config.Provider.Azure.Location = "test-location"
|
||||
config.Provider.Azure.UserAssignedIdentity = "test-identity"
|
||||
config.Provider.Azure.Measurements[8] = []byte("00000000000000000000000000000000")
|
||||
config.Provider.Azure.Measurements[9] = []byte("11111111111111111111111111111111")
|
||||
switch csp {
|
||||
case cloudprovider.Azure:
|
||||
conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
|
||||
conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
|
||||
conf.Provider.Azure.Location = "test-location"
|
||||
conf.Provider.Azure.UserAssignedIdentity = "test-identity"
|
||||
conf.Provider.Azure.Measurements[8] = []byte("00000000000000000000000000000000")
|
||||
conf.Provider.Azure.Measurements[9] = []byte("11111111111111111111111111111111")
|
||||
case cloudprovider.GCP:
|
||||
conf.Provider.GCP.Region = "test-region"
|
||||
conf.Provider.GCP.Project = "test-project"
|
||||
conf.Provider.GCP.Zone = "test-zone"
|
||||
conf.Provider.GCP.Measurements[8] = []byte("00000000000000000000000000000000")
|
||||
conf.Provider.GCP.Measurements[9] = []byte("11111111111111111111111111111111")
|
||||
case cloudprovider.QEMU:
|
||||
conf.Provider.QEMU.Measurements[8] = []byte("00000000000000000000000000000000")
|
||||
conf.Provider.QEMU.Measurements[9] = []byte("11111111111111111111111111111111")
|
||||
}
|
||||
|
||||
config.Provider.GCP.Region = "test-region"
|
||||
config.Provider.GCP.Project = "test-project"
|
||||
config.Provider.GCP.Zone = "test-zone"
|
||||
config.Provider.GCP.Measurements[8] = []byte("00000000000000000000000000000000")
|
||||
config.Provider.GCP.Measurements[9] = []byte("11111111111111111111111111111111")
|
||||
|
||||
config.Provider.QEMU.Measurements[8] = []byte("00000000000000000000000000000000")
|
||||
config.Provider.QEMU.Measurements[9] = []byte("11111111111111111111111111111111")
|
||||
|
||||
config.RemoveProviderExcept(csp)
|
||||
return config
|
||||
conf.RemoveProviderExcept(csp)
|
||||
return conf
|
||||
}
|
||||
|
||||
type stubLicenseClient struct{}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
"github.com/edgelesssys/constellation/internal/crypto/testvector"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
@ -146,7 +147,7 @@ func TestRecover(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
fileHandler := file.NewHandler(fs)
|
||||
|
||||
config := defaultConfigWithExpectedMeasurements(t, cloudprovider.FromString(tc.existingState.CloudProvider))
|
||||
config := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.FromString(tc.existingState.CloudProvider))
|
||||
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config))
|
||||
|
||||
require.NoError(fileHandler.WriteJSON("constellation-mastersecret.json", masterSecret{Key: tc.masterSecret.Secret, Salt: tc.masterSecret.Salt}, file.OptNone))
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/atls"
|
||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/constants"
|
||||
"github.com/edgelesssys/constellation/internal/file"
|
||||
"github.com/edgelesssys/constellation/internal/grpc/dialer"
|
||||
@ -190,7 +191,7 @@ func TestVerify(t *testing.T) {
|
||||
}
|
||||
fileHandler := file.NewHandler(tc.setupFs(require))
|
||||
|
||||
config := defaultConfigWithExpectedMeasurements(t, tc.provider)
|
||||
config := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.provider)
|
||||
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config))
|
||||
if tc.idFile != nil {
|
||||
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.idFile, file.OptNone))
|
||||
|
@ -2,15 +2,12 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/api/iterator"
|
||||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||
adminpb "google.golang.org/genproto/googleapis/iam/admin/v1"
|
||||
iampb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type stubOperation struct {
|
||||
@ -436,54 +433,6 @@ func (a stubInstanceGroupManagersAPI) ListManagedInstances(ctx context.Context,
|
||||
return a.listIterator
|
||||
}
|
||||
|
||||
type stubIAMAPI struct {
|
||||
serviceAccountKeyData []byte
|
||||
createErr error
|
||||
createKeyErr error
|
||||
deleteServiceAccountErr error
|
||||
}
|
||||
|
||||
func (a stubIAMAPI) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a stubIAMAPI) CreateServiceAccount(ctx context.Context, req *adminpb.CreateServiceAccountRequest, opts ...gax.CallOption) (*adminpb.ServiceAccount, error) {
|
||||
if a.createErr != nil {
|
||||
return nil, a.createErr
|
||||
}
|
||||
return &adminpb.ServiceAccount{
|
||||
Name: "name",
|
||||
ProjectId: "project-id",
|
||||
UniqueId: "unique-id",
|
||||
Email: "email",
|
||||
DisplayName: "display-name",
|
||||
Description: "description",
|
||||
Oauth2ClientId: "oauth2-client-id",
|
||||
Disabled: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a stubIAMAPI) CreateServiceAccountKey(ctx context.Context, req *adminpb.CreateServiceAccountKeyRequest, opts ...gax.CallOption) (*adminpb.ServiceAccountKey, error) {
|
||||
if a.createKeyErr != nil {
|
||||
return nil, a.createKeyErr
|
||||
}
|
||||
return &adminpb.ServiceAccountKey{
|
||||
Name: "name",
|
||||
PrivateKeyType: adminpb.ServiceAccountPrivateKeyType_TYPE_GOOGLE_CREDENTIALS_FILE,
|
||||
KeyAlgorithm: adminpb.ServiceAccountKeyAlgorithm_KEY_ALG_RSA_2048,
|
||||
PrivateKeyData: a.serviceAccountKeyData,
|
||||
PublicKeyData: []byte("public-key-data"),
|
||||
ValidAfterTime: timestamppb.New(time.Time{}),
|
||||
ValidBeforeTime: timestamppb.New(time.Time{}),
|
||||
KeyOrigin: adminpb.ServiceAccountKeyOrigin_GOOGLE_PROVIDED,
|
||||
KeyType: adminpb.ListServiceAccountKeysRequest_USER_MANAGED,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a stubIAMAPI) DeleteServiceAccount(ctx context.Context, req *adminpb.DeleteServiceAccountRequest, opts ...gax.CallOption) error {
|
||||
return a.deleteServiceAccountErr
|
||||
}
|
||||
|
||||
type stubProjectsAPI struct {
|
||||
getPolicyErr error
|
||||
setPolicyErr error
|
||||
|
@ -55,7 +55,6 @@ type Client struct {
|
||||
uid string
|
||||
zone string
|
||||
region string
|
||||
serviceAccount string
|
||||
|
||||
// loadbalancer
|
||||
loadbalancerIP string
|
||||
@ -270,7 +269,6 @@ func (c *Client) GetState() state.ConstellationState {
|
||||
GCPFirewalls: c.firewalls,
|
||||
GCPNetwork: c.network,
|
||||
GCPSubnetwork: c.subnetwork,
|
||||
GCPServiceAccount: c.serviceAccount,
|
||||
GCPLoadbalancerIPname: c.loadbalancerIPname,
|
||||
}
|
||||
for _, lb := range c.loadbalancers {
|
||||
@ -297,7 +295,6 @@ func (c *Client) SetState(stat state.ConstellationState) {
|
||||
c.controlPlaneTemplate = stat.GCPControlPlaneInstanceTemplate
|
||||
c.loadbalancerIPname = stat.GCPLoadbalancerIPname
|
||||
c.loadbalancerIP = stat.LoadBalancerIP
|
||||
c.serviceAccount = stat.GCPServiceAccount
|
||||
for _, lbName := range stat.GCPLoadbalancers {
|
||||
lb := &loadBalancer{
|
||||
name: lbName,
|
||||
|
@ -69,7 +69,6 @@ func TestSetGetState(t *testing.T) {
|
||||
assert.Equal(state.GCPFirewalls, client.firewalls)
|
||||
assert.Equal(state.GCPControlPlaneInstanceTemplate, client.controlPlaneTemplate)
|
||||
assert.Equal(state.GCPWorkerInstanceTemplate, client.workerTemplate)
|
||||
assert.Equal(state.GCPServiceAccount, client.serviceAccount)
|
||||
assert.Equal(state.LoadBalancerIP, client.loadbalancerIP)
|
||||
for _, lb := range client.loadbalancers {
|
||||
assert.Contains(state.GCPLoadbalancers, lb.name)
|
||||
@ -97,7 +96,6 @@ func TestSetGetState(t *testing.T) {
|
||||
firewalls: state.GCPFirewalls,
|
||||
workerTemplate: state.GCPWorkerInstanceTemplate,
|
||||
controlPlaneTemplate: state.GCPControlPlaneInstanceTemplate,
|
||||
serviceAccount: state.GCPServiceAccount,
|
||||
loadbalancerIP: state.LoadBalancerIP,
|
||||
loadbalancerIPname: state.GCPLoadbalancerIPname,
|
||||
}
|
||||
@ -111,18 +109,6 @@ func TestSetGetState(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{}
|
||||
require.NoError(client.init("project", "zone", "region", "name"))
|
||||
assert.Equal("project", client.project)
|
||||
assert.Equal("zone", client.zone)
|
||||
assert.Equal("region", client.region)
|
||||
assert.Equal("name", client.name)
|
||||
}
|
||||
|
||||
func TestBuildResourceName(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
clientUID string
|
||||
@ -209,6 +195,18 @@ func TestResourceURI(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
client := Client{}
|
||||
require.NoError(client.init("project", "zone", "region", "name"))
|
||||
assert.Equal("project", client.project)
|
||||
assert.Equal("zone", client.zone)
|
||||
assert.Equal("region", client.region)
|
||||
assert.Equal("name", client.name)
|
||||
}
|
||||
|
||||
func TestCloseAll(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -1,120 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/gcpshared"
|
||||
adminpb "google.golang.org/genproto/googleapis/iam/admin/v1"
|
||||
)
|
||||
|
||||
// CreateServiceAccount creates a new GCP service account and returns an account key as service account URI.
|
||||
func (c *Client) CreateServiceAccount(ctx context.Context, input ServiceAccountInput) (string, error) {
|
||||
insertInput := insertServiceAccountInput{
|
||||
Project: c.project,
|
||||
AccountID: "constellation-app-" + c.uid,
|
||||
DisplayName: "constellation-app-" + c.uid,
|
||||
Description: "This service account belongs to a Constellation cluster.",
|
||||
}
|
||||
|
||||
email, err := c.insertServiceAccount(ctx, insertInput)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.serviceAccount = email
|
||||
|
||||
iamInput := input.addIAMPolicyBindingInput(c.serviceAccount)
|
||||
if err := c.addIAMPolicyBindings(ctx, iamInput); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key, err := c.createServiceAccountKey(ctx, email)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return key.ToCloudServiceAccountURI(), nil
|
||||
}
|
||||
|
||||
func (c *Client) TerminateServiceAccount(ctx context.Context) error {
|
||||
if c.serviceAccount == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
req := &adminpb.DeleteServiceAccountRequest{
|
||||
Name: "projects/-/serviceAccounts/" + c.serviceAccount,
|
||||
}
|
||||
if err := c.iamAPI.DeleteServiceAccount(ctx, req); err != nil && !isNotFoundError(err) {
|
||||
return fmt.Errorf("deleting service account: %w", err)
|
||||
}
|
||||
|
||||
c.serviceAccount = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServiceAccountInput struct {
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func (i ServiceAccountInput) addIAMPolicyBindingInput(serviceAccount string) AddIAMPolicyBindingInput {
|
||||
iamPolicyBindingInput := AddIAMPolicyBindingInput{
|
||||
Bindings: make([]PolicyBinding, len(i.Roles)),
|
||||
}
|
||||
for i, role := range i.Roles {
|
||||
iamPolicyBindingInput.Bindings[i] = PolicyBinding{
|
||||
ServiceAccount: serviceAccount,
|
||||
Role: role,
|
||||
}
|
||||
}
|
||||
return iamPolicyBindingInput
|
||||
}
|
||||
|
||||
func (c *Client) insertServiceAccount(ctx context.Context, input insertServiceAccountInput) (string, error) {
|
||||
req := input.createServiceAccountRequest()
|
||||
account, err := c.iamAPI.CreateServiceAccount(ctx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return account.Email, nil
|
||||
}
|
||||
|
||||
func (c *Client) createServiceAccountKey(ctx context.Context, email string) (gcpshared.ServiceAccountKey, error) {
|
||||
req := createServiceAccountKeyRequest(email)
|
||||
key, err := c.iamAPI.CreateServiceAccountKey(ctx, req)
|
||||
if err != nil {
|
||||
return gcpshared.ServiceAccountKey{}, fmt.Errorf("creating service account key: %w", err)
|
||||
}
|
||||
var serviceAccountKey gcpshared.ServiceAccountKey
|
||||
if err := json.Unmarshal(key.PrivateKeyData, &serviceAccountKey); err != nil {
|
||||
return gcpshared.ServiceAccountKey{}, fmt.Errorf("decoding service account key JSON: %w", err)
|
||||
}
|
||||
|
||||
return serviceAccountKey, nil
|
||||
}
|
||||
|
||||
// insertServiceAccountInput is the input for a createServiceAccount operation.
|
||||
type insertServiceAccountInput struct {
|
||||
Project string
|
||||
AccountID string
|
||||
DisplayName string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (c insertServiceAccountInput) createServiceAccountRequest() *adminpb.CreateServiceAccountRequest {
|
||||
return &adminpb.CreateServiceAccountRequest{
|
||||
Name: "projects/" + c.Project,
|
||||
AccountId: c.AccountID,
|
||||
ServiceAccount: &adminpb.ServiceAccount{
|
||||
DisplayName: c.DisplayName,
|
||||
Description: c.Description,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createServiceAccountKeyRequest(email string) *adminpb.CreateServiceAccountKeyRequest {
|
||||
return &adminpb.CreateServiceAccountKeyRequest{
|
||||
Name: "projects/-/serviceAccounts/" + email,
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/internal/gcpshared"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateServiceAccount(t *testing.T) {
|
||||
require := require.New(t)
|
||||
someErr := errors.New("someErr")
|
||||
key := gcpshared.ServiceAccountKey{
|
||||
Type: "type",
|
||||
ProjectID: "project-id",
|
||||
PrivateKeyID: "private-key-id",
|
||||
PrivateKey: "private-key",
|
||||
ClientEmail: "client-email",
|
||||
ClientID: "client-id",
|
||||
AuthURI: "auth-uri",
|
||||
TokenURI: "token-uri",
|
||||
AuthProviderX509CertURL: "auth-provider-x509-cert-url",
|
||||
ClientX509CertURL: "client-x509-cert-url",
|
||||
}
|
||||
keyData, err := json.Marshal(key)
|
||||
require.NoError(err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
iamAPI iamAPI
|
||||
projectsAPI stubProjectsAPI
|
||||
input ServiceAccountInput
|
||||
wantErr bool
|
||||
}{
|
||||
"successful create": {
|
||||
iamAPI: stubIAMAPI{serviceAccountKeyData: keyData},
|
||||
input: ServiceAccountInput{
|
||||
Roles: []string{"someRole"},
|
||||
},
|
||||
},
|
||||
"successful create with roles": {
|
||||
iamAPI: stubIAMAPI{serviceAccountKeyData: keyData},
|
||||
},
|
||||
"creating account fails": {
|
||||
iamAPI: stubIAMAPI{createErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"creating account key fails": {
|
||||
iamAPI: stubIAMAPI{createKeyErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"key data missing": {
|
||||
iamAPI: stubIAMAPI{},
|
||||
wantErr: true,
|
||||
},
|
||||
"key data corrupt": {
|
||||
iamAPI: stubIAMAPI{serviceAccountKeyData: []byte("invalid key data")},
|
||||
wantErr: true,
|
||||
},
|
||||
"retrieving iam policy bindings fails": {
|
||||
iamAPI: stubIAMAPI{},
|
||||
projectsAPI: stubProjectsAPI{getPolicyErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"setting iam policy bindings fails": {
|
||||
iamAPI: stubIAMAPI{},
|
||||
projectsAPI: stubProjectsAPI{setPolicyErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
iamAPI: tc.iamAPI,
|
||||
projectsAPI: tc.projectsAPI,
|
||||
}
|
||||
|
||||
serviceAccountKey, err := client.CreateServiceAccount(ctx, tc.input)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(key.ToCloudServiceAccountURI(), serviceAccountKey)
|
||||
assert.Equal("email", client.serviceAccount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminateServiceAccount(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
iamAPI iamAPI
|
||||
wantErr bool
|
||||
}{
|
||||
"delete works": {
|
||||
iamAPI: stubIAMAPI{},
|
||||
},
|
||||
"delete fails": {
|
||||
iamAPI: stubIAMAPI{
|
||||
deleteServiceAccountErr: errors.New("someErr"),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
client := Client{
|
||||
project: "project",
|
||||
zone: "zone",
|
||||
name: "name",
|
||||
uid: "uid",
|
||||
serviceAccount: "service-account",
|
||||
iamAPI: tc.iamAPI,
|
||||
}
|
||||
|
||||
err := client.TerminateServiceAccount(ctx)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -162,8 +162,8 @@ type GCPConfig struct {
|
||||
// Type of a node's state disk. The type influences boot time and I/O performance. See: https://cloud.google.com/compute/docs/disks#disk-types
|
||||
StateDiskType string `yaml:"stateDiskType" validate:"oneof=pd-standard pd-balanced pd-ssd"`
|
||||
// description: |
|
||||
// Roles added to service account.
|
||||
ServiceAccountRoles []string `yaml:"serviceAccountRoles"`
|
||||
// Path of service account key file. For needed service account roles, see https://constellation-docs.edgeless.systems/constellation/latest/#/getting-started/install?id=authorization
|
||||
ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath"`
|
||||
// description: |
|
||||
// Expected confidential VM measurements.
|
||||
Measurements Measurements `yaml:"measurements"`
|
||||
@ -232,20 +232,14 @@ func Default() *Config {
|
||||
EnforcedMeasurements: []uint32{8, 9, 11, 12},
|
||||
},
|
||||
GCP: &GCPConfig{
|
||||
Project: "",
|
||||
Region: "",
|
||||
Zone: "",
|
||||
Image: "projects/constellation-images/global/images/constellation-v1-5-0",
|
||||
ServiceAccountRoles: []string{
|
||||
"roles/compute.instanceAdmin.v1",
|
||||
"roles/compute.networkAdmin",
|
||||
"roles/compute.securityAdmin",
|
||||
"roles/storage.admin",
|
||||
"roles/iam.serviceAccountUser",
|
||||
},
|
||||
StateDiskType: "pd-ssd",
|
||||
Measurements: copyPCRMap(gcpPCRs),
|
||||
EnforcedMeasurements: []uint32{0, 8, 9, 11, 12},
|
||||
Project: "",
|
||||
Region: "",
|
||||
Zone: "",
|
||||
Image: "projects/constellation-images/global/images/constellation-v1-5-0",
|
||||
StateDiskType: "pd-ssd",
|
||||
ServiceAccountKeyPath: "serviceAccountKey.json",
|
||||
Measurements: copyPCRMap(gcpPCRs),
|
||||
EnforcedMeasurements: []uint32{0, 8, 9, 11, 12},
|
||||
},
|
||||
QEMU: &QEMUConfig{
|
||||
Measurements: copyPCRMap(qemuPCRs),
|
||||
|
@ -245,11 +245,11 @@ func init() {
|
||||
GCPConfigDoc.Fields[4].Note = ""
|
||||
GCPConfigDoc.Fields[4].Description = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://cloud.google.com/compute/docs/disks#disk-types"
|
||||
GCPConfigDoc.Fields[4].Comments[encoder.LineComment] = "Type of a node's state disk. The type influences boot time and I/O performance. See: https://cloud.google.com/compute/docs/disks#disk-types"
|
||||
GCPConfigDoc.Fields[5].Name = "serviceAccountRoles"
|
||||
GCPConfigDoc.Fields[5].Type = "[]string"
|
||||
GCPConfigDoc.Fields[5].Name = "serviceAccountKeyPath"
|
||||
GCPConfigDoc.Fields[5].Type = "string"
|
||||
GCPConfigDoc.Fields[5].Note = ""
|
||||
GCPConfigDoc.Fields[5].Description = "Roles added to service account."
|
||||
GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "Roles added to service account."
|
||||
GCPConfigDoc.Fields[5].Description = "Path of service account key file. For needed service account roles, see https://constellation-docs.edgeless.systems/constellation/latest/#/getting-started/install?id=authorization"
|
||||
GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "Path of service account key file. For needed service account roles, see https://constellation-docs.edgeless.systems/constellation/latest/#/getting-started/install?id=authorization"
|
||||
GCPConfigDoc.Fields[6].Name = "measurements"
|
||||
GCPConfigDoc.Fields[6].Type = "Measurements"
|
||||
GCPConfigDoc.Fields[6].Note = ""
|
||||
|
@ -25,7 +25,6 @@ type ConstellationState struct {
|
||||
GCPProject string `json:"gcpproject,omitempty"`
|
||||
GCPZone string `json:"gcpzone,omitempty"`
|
||||
GCPRegion string `json:"gcpregion,omitempty"`
|
||||
GCPServiceAccount string `json:"gcpserviceaccount,omitempty"`
|
||||
|
||||
AzureWorkerInstances cloudtypes.Instances `json:"azureworkers,omitempty"`
|
||||
AzureControlPlaneInstances cloudtypes.Instances `json:"azurecontrolplanes,omitempty"`
|
||||
|
Loading…
Reference in New Issue
Block a user