Manually manage GCP service accounts

This commit is contained in:
katexochen 2022-08-23 17:49:55 +02:00 committed by Paul Meyer
parent f9c70d5c5a
commit e761c9bf97
19 changed files with 186 additions and 555 deletions

View file

@ -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. - 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. - Improve rollback on GCP resource termination. You can now terminate multiple times.
- Implement SSH peer to peer distribution between debugd nodes. - Implement SSH peer to peer distribution between debugd nodes.
- GCP service account can now be managed manually.
### Changed ### Changed
<!-- For changes in existing functionality. --> <!-- For changes in existing functionality. -->

View file

@ -15,12 +15,10 @@ type gcpclient interface {
CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error
CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error
CreateLoadBalancers(ctx context.Context) error CreateLoadBalancers(ctx context.Context) error
CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error)
TerminateFirewall(ctx context.Context) error TerminateFirewall(ctx context.Context) error
TerminateVPCs(context.Context) error TerminateVPCs(context.Context) error
TerminateLoadBalancers(context.Context) error TerminateLoadBalancers(context.Context) error
TerminateInstances(context.Context) error TerminateInstances(context.Context) error
TerminateServiceAccount(ctx context.Context) error
Close() error Close() error
} }

View file

@ -11,7 +11,6 @@ import (
"github.com/edgelesssys/constellation/internal/azureshared" "github.com/edgelesssys/constellation/internal/azureshared"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/cloud/cloudtypes" "github.com/edgelesssys/constellation/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/internal/gcpshared"
"github.com/edgelesssys/constellation/internal/state" "github.com/edgelesssys/constellation/internal/state"
"go.uber.org/goleak" "go.uber.org/goleak"
) )
@ -244,7 +243,6 @@ type fakeGcpClient struct {
uid string uid string
name string name string
zone string zone string
serviceAccount string
loadbalancers []string loadbalancers []string
} }
@ -264,7 +262,6 @@ func (c *fakeGcpClient) GetState() state.ConstellationState {
Name: c.name, Name: c.name,
UID: c.uid, UID: c.uid,
GCPZone: c.zone, GCPZone: c.zone,
GCPServiceAccount: c.serviceAccount,
GCPLoadbalancers: c.loadbalancers, GCPLoadbalancers: c.loadbalancers,
} }
} }
@ -283,7 +280,6 @@ func (c *fakeGcpClient) SetState(stat state.ConstellationState) {
c.name = stat.Name c.name = stat.Name
c.uid = stat.UID c.uid = stat.UID
c.zone = stat.GCPZone c.zone = stat.GCPZone
c.serviceAccount = stat.GCPServiceAccount
c.loadbalancers = stat.GCPLoadbalancers c.loadbalancers = stat.GCPLoadbalancers
} }
@ -321,22 +317,6 @@ func (c *fakeGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateI
return nil 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 { func (c *fakeGcpClient) CreateLoadBalancers(ctx context.Context) error {
c.loadbalancers = []string{"kube-lb", "boot-lb", "verify-lb"} c.loadbalancers = []string{"kube-lb", "boot-lb", "verify-lb"}
return nil return nil
@ -369,11 +349,6 @@ func (c *fakeGcpClient) TerminateInstances(context.Context) error {
return nil return nil
} }
func (c *fakeGcpClient) TerminateServiceAccount(context.Context) error {
c.serviceAccount = ""
return nil
}
func (c *fakeGcpClient) TerminateLoadBalancers(context.Context) error { func (c *fakeGcpClient) TerminateLoadBalancers(context.Context) error {
c.loadbalancers = nil c.loadbalancers = nil
return nil return nil
@ -384,23 +359,20 @@ func (c *fakeGcpClient) Close() error {
} }
type stubGcpClient struct { type stubGcpClient struct {
terminateFirewallCalled bool terminateFirewallCalled bool
terminateInstancesCalled bool terminateInstancesCalled bool
terminateVPCsCalled bool terminateVPCsCalled bool
terminateServiceAccountCalled bool closeCalled bool
closeCalled bool
createVPCsErr error createVPCsErr error
createFirewallErr error createFirewallErr error
createInstancesErr error createInstancesErr error
createServiceAccountErr error createLoadBalancerErr error
createLoadBalancerErr error terminateFirewallErr error
terminateFirewallErr error terminateVPCsErr error
terminateVPCsErr error terminateInstancesErr error
terminateInstancesErr error terminateLoadBalancerErr error
terminateServiceAccountErr error closeErr error
terminateLoadBalancerErr error
closeErr error
} }
func (c *stubGcpClient) GetState() state.ConstellationState { func (c *stubGcpClient) GetState() state.ConstellationState {
@ -422,10 +394,6 @@ func (c *stubGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateI
return c.createInstancesErr 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 { func (c *stubGcpClient) CreateLoadBalancers(ctx context.Context) error {
return c.createLoadBalancerErr return c.createLoadBalancerErr
} }
@ -445,11 +413,6 @@ func (c *stubGcpClient) TerminateInstances(context.Context) error {
return c.terminateInstancesErr return c.terminateInstancesErr
} }
func (c *stubGcpClient) TerminateServiceAccount(context.Context) error {
c.terminateServiceAccountCalled = true
return c.terminateServiceAccountErr
}
func (c *stubGcpClient) TerminateLoadBalancers(context.Context) error { func (c *stubGcpClient) TerminateLoadBalancers(context.Context) error {
return c.terminateLoadBalancerErr return c.terminateLoadBalancerErr
} }

View file

@ -34,18 +34,7 @@ func (c *ServiceAccountCreator) Create(ctx context.Context, stat state.Constella
provider := cloudprovider.FromString(stat.CloudProvider) provider := cloudprovider.FromString(stat.CloudProvider)
switch provider { switch provider {
case cloudprovider.GCP: case cloudprovider.GCP:
cl, err := c.newGCPClient(ctx) return "", state.ConstellationState{}, fmt.Errorf("creating service account not supported for GCP")
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
case cloudprovider.Azure: case cloudprovider.Azure:
cl, err := c.newAzureClient(stat.AzureSubscription, stat.AzureTenant) cl, err := c.newAzureClient(stat.AzureSubscription, stat.AzureTenant)
if err != nil { 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, func (c *ServiceAccountCreator) createServiceAccountAzure(ctx context.Context, cl azureclient,
stat state.ConstellationState, _ *config.Config, stat state.ConstellationState, _ *config.Config,
) (string, state.ConstellationState, error) { ) (string, state.ConstellationState, error) {

View file

@ -6,27 +6,12 @@ import (
"testing" "testing"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider" "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/config"
"github.com/edgelesssys/constellation/internal/state" "github.com/edgelesssys/constellation/internal/state"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestServiceAccountCreator(t *testing.T) { 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 { someAzureState := func() state.ConstellationState {
return state.ConstellationState{ return state.ConstellationState{
CloudProvider: cloudprovider.Azure.String(), CloudProvider: cloudprovider.Azure.String(),
@ -42,32 +27,6 @@ func TestServiceAccountCreator(t *testing.T) {
wantErr bool wantErr bool
wantStateMutator func(*state.ConstellationState) 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": { "azure": {
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) { newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
return &fakeAzureClient{}, nil return &fakeAzureClient{}, nil

View file

@ -65,7 +65,8 @@ func (t *Terminator) terminateGCP(ctx context.Context, cl gcpclient, state state
if err := cl.TerminateVPCs(ctx); err != nil { if err := cl.TerminateVPCs(ctx); err != nil {
return err return err
} }
return cl.TerminateServiceAccount(ctx)
return nil
} }
func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error { func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error {

View file

@ -29,7 +29,6 @@ func TestTerminator(t *testing.T) {
GCPControlPlaneInstanceTemplate: "template", GCPControlPlaneInstanceTemplate: "template",
GCPNetwork: "network", GCPNetwork: "network",
GCPFirewalls: []string{"a", "b", "c"}, GCPFirewalls: []string{"a", "b", "c"},
GCPServiceAccount: "service-account@project.iam.gserviceaccount.com",
} }
} }
someAzureState := func() state.ConstellationState { someAzureState := func() state.ConstellationState {
@ -80,11 +79,6 @@ func TestTerminator(t *testing.T) {
state: someGCPState(), state: someGCPState(),
wantErr: true, wantErr: true,
}, },
"gcp terminateServiceAccount error": {
gcpclient: &stubGcpClient{terminateServiceAccountErr: someErr},
state: someGCPState(),
wantErr: true,
},
"azure": { "azure": {
azureclient: &stubAzureClient{}, azureclient: &stubAzureClient{},
state: someAzureState(), state: someAzureState(),
@ -135,7 +129,6 @@ func TestTerminator(t *testing.T) {
assert.True(cl.terminateFirewallCalled) assert.True(cl.terminateFirewallCalled)
assert.True(cl.terminateInstancesCalled) assert.True(cl.terminateInstancesCalled)
assert.True(cl.terminateVPCsCalled) assert.True(cl.terminateVPCsCalled)
assert.True(cl.terminateServiceAccountCalled)
assert.True(cl.closeCalled) assert.True(cl.closeCalled)
case cloudprovider.Azure: case cloudprovider.Azure:
cl := tc.azureclient.(*stubAzureClient) cl := tc.azureclient.(*stubAzureClient)

View file

@ -24,6 +24,7 @@ import (
"github.com/edgelesssys/constellation/internal/crypto" "github.com/edgelesssys/constellation/internal/crypto"
"github.com/edgelesssys/constellation/internal/deploy/ssh" "github.com/edgelesssys/constellation/internal/deploy/ssh"
"github.com/edgelesssys/constellation/internal/file" "github.com/edgelesssys/constellation/internal/file"
"github.com/edgelesssys/constellation/internal/gcpshared"
"github.com/edgelesssys/constellation/internal/grpc/dialer" "github.com/edgelesssys/constellation/internal/grpc/dialer"
grpcRetry "github.com/edgelesssys/constellation/internal/grpc/retry" grpcRetry "github.com/edgelesssys/constellation/internal/grpc/retry"
"github.com/edgelesssys/constellation/internal/license" "github.com/edgelesssys/constellation/internal/license"
@ -116,13 +117,22 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
return err return err
} }
cmd.Println("Creating service account ...") var serviceAccURI string
serviceAccount, stat, err := serviceAccCreator.Create(cmd.Context(), stat, config) // Temporary legacy flow for Azure.
if err != nil { if provider == cloudprovider.Azure {
return err cmd.Println("Creating service account ...")
} serviceAccURI, stat, err = serviceAccCreator.Create(cmd.Context(), stat, config)
if err := fileHandler.WriteJSON(constants.StateFilename, stat, file.OptOverwrite); err != nil { if err != nil {
return err 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) workers, err := getScalingGroupsFromState(stat, config)
@ -150,7 +160,7 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
StorageUri: kms.NoStoreURI, StorageUri: kms.NoStoreURI,
KeyEncryptionKeyId: "", KeyEncryptionKeyId: "",
UseExistingKek: false, UseExistingKek: false,
CloudServiceAccountUri: serviceAccount, CloudServiceAccountUri: serviceAccURI,
KubernetesVersion: config.KubernetesVersion, KubernetesVersion: config.KubernetesVersion,
SshUserKeys: ssh.ToProtoSlice(sshUsers), SshUserKeys: ssh.ToProtoSlice(sshUsers),
HelmDeployments: helmDeployments, HelmDeployments: helmDeployments,
@ -352,6 +362,29 @@ func readIPFromIDFile(fileHandler file.Handler) (string, error) {
return idFile.IP, nil 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) { func getScalingGroupsFromState(stat state.ConstellationState, config *config.Config) (workers cloudtypes.ScalingGroup, err error) {
switch cloudprovider.FromString(stat.CloudProvider) { switch cloudprovider.FromString(stat.CloudProvider) {
case cloudprovider.GCP: case cloudprovider.GCP:

View file

@ -20,6 +20,7 @@ import (
"github.com/edgelesssys/constellation/internal/config" "github.com/edgelesssys/constellation/internal/config"
"github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/constants"
"github.com/edgelesssys/constellation/internal/file" "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/atlscredentials"
"github.com/edgelesssys/constellation/internal/grpc/dialer" "github.com/edgelesssys/constellation/internal/grpc/dialer"
"github.com/edgelesssys/constellation/internal/grpc/testdialer" "github.com/edgelesssys/constellation/internal/grpc/testdialer"
@ -43,16 +44,19 @@ func TestInitArgumentValidation(t *testing.T) {
} }
func TestInitialize(t *testing.T) { func TestInitialize(t *testing.T) {
testGcpState := state.ConstellationState{ testGcpState := &state.ConstellationState{
CloudProvider: "GCP", CloudProvider: "GCP",
GCPWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}}, GCPWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
} }
testAzureState := state.ConstellationState{ gcpServiceAccKey := &gcpshared.ServiceAccountKey{
Type: "service_account",
}
testAzureState := &state.ConstellationState{
CloudProvider: "Azure", CloudProvider: "Azure",
AzureWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}}, AzureWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
AzureResourceGroup: "test", AzureResourceGroup: "test",
} }
testQemuState := state.ConstellationState{ testQemuState := &state.ConstellationState{
CloudProvider: "QEMU", CloudProvider: "QEMU",
QEMUWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}}, QEMUWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
} }
@ -61,77 +65,86 @@ func TestInitialize(t *testing.T) {
OwnerId: []byte("ownerID"), OwnerId: []byte("ownerID"),
ClusterId: []byte("clusterID"), ClusterId: []byte("clusterID"),
} }
serviceAccPath := "/test/service-account.json"
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
existingState state.ConstellationState state *state.ConstellationState
existingIDFile clusterIDsFile existingIDFile *clusterIDsFile
serviceAccountCreator stubServiceAccountCreator serviceAccCreator serviceAccountCreator
helmLoader stubHelmLoader configMutator func(*config.Config)
initServerAPI *stubInitServer serviceAccKey *gcpshared.ServiceAccountKey
endpointFlag string helmLoader stubHelmLoader
setAutoscaleFlag bool initServerAPI *stubInitServer
wantErr bool endpointFlag string
setAutoscaleFlag bool
wantErr bool
}{ }{
"initialize some gcp instances": { "initialize some gcp instances": {
existingState: testGcpState, state: testGcpState,
existingIDFile: clusterIDsFile{IP: "192.0.2.1"}, existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
initServerAPI: &stubInitServer{initResp: testInitResp}, serviceAccKey: gcpServiceAccKey,
},
"initialize some azure instances": {
existingState: testAzureState,
existingIDFile: clusterIDsFile{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initResp: testInitResp}, 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": { "initialize some qemu instances": {
existingState: testQemuState, state: testQemuState,
existingIDFile: clusterIDsFile{IP: "192.0.2.1"}, existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initResp: testInitResp}, initServerAPI: &stubInitServer{initResp: testInitResp},
}, },
"initialize gcp with autoscaling": { "initialize gcp with autoscaling": {
existingState: testGcpState, state: testGcpState,
existingIDFile: clusterIDsFile{IP: "192.0.2.1"}, existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
initServerAPI: &stubInitServer{initResp: testInitResp}, initServerAPI: &stubInitServer{initResp: testInitResp},
setAutoscaleFlag: true, setAutoscaleFlag: true,
}, },
"initialize azure with autoscaling": { "initialize azure with autoscaling": {
existingState: testAzureState, state: testAzureState,
existingIDFile: clusterIDsFile{IP: "192.0.2.1"}, existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initResp: testInitResp}, serviceAccCreator: &stubServiceAccountCreator{},
setAutoscaleFlag: true, initServerAPI: &stubInitServer{initResp: testInitResp},
setAutoscaleFlag: true,
}, },
"initialize with endpoint flag": { "initialize with endpoint flag": {
existingState: testGcpState, state: testGcpState,
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
initServerAPI: &stubInitServer{initResp: testInitResp}, initServerAPI: &stubInitServer{initResp: testInitResp},
endpointFlag: "192.0.2.1", endpointFlag: "192.0.2.1",
}, },
"empty state": { "empty state": {
existingState: state.ConstellationState{}, state: &state.ConstellationState{},
existingIDFile: clusterIDsFile{IP: "192.0.2.1"}, existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{}, initServerAPI: &stubInitServer{},
wantErr: true, wantErr: true,
}, },
"neither endpoint flag nor id file": { "neither endpoint flag nor id file": {
existingState: state.ConstellationState{}, state: &state.ConstellationState{},
initServerAPI: &stubInitServer{}, wantErr: true,
wantErr: true,
}, },
"init call fails": { "init call fails": {
existingState: testGcpState, state: testGcpState,
existingIDFile: clusterIDsFile{IP: "192.0.2.1"}, existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initErr: someErr}, initServerAPI: &stubInitServer{initErr: someErr},
wantErr: true, wantErr: true,
}, },
"fail to create service account": { "fail to create service account": {
existingState: testGcpState, state: testAzureState,
existingIDFile: clusterIDsFile{IP: "192.0.2.1"}, existingIDFile: &clusterIDsFile{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{}, initServerAPI: &stubInitServer{},
serviceAccountCreator: stubServiceAccountCreator{createErr: someErr}, serviceAccCreator: &stubServiceAccountCreator{createErr: someErr},
wantErr: true, wantErr: true,
}, },
"fail to load helm charts": { "fail to load helm charts": {
existingState: testGcpState, state: testGcpState,
helmLoader: stubHelmLoader{loadErr: someErr}, helmLoader: stubHelmLoader{loadErr: someErr},
initServerAPI: &stubInitServer{initResp: testInitResp}, initServerAPI: &stubInitServer{initResp: testInitResp},
wantErr: true, wantErr: true,
@ -143,6 +156,7 @@ func TestInitialize(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
// Networking
netDialer := testdialer.NewBufconnDialer() netDialer := testdialer.NewBufconnDialer()
newDialer := func(*cloudcmd.Validator) *dialer.Dialer { newDialer := func(*cloudcmd.Validator) *dialer.Dialer {
return dialer.New(nil, nil, netDialer) return dialer.New(nil, nil, netDialer)
@ -155,30 +169,44 @@ func TestInitialize(t *testing.T) {
go initServer.Serve(listener) go initServer.Serve(listener)
defer initServer.GracefulStop() defer initServer.GracefulStop()
// Command
cmd := NewInitCmd() cmd := NewInitCmd()
var out bytes.Buffer var out bytes.Buffer
cmd.SetOut(&out) cmd.SetOut(&out)
var errOut bytes.Buffer var errOut bytes.Buffer
cmd.SetErr(&errOut) 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)) // Flags
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config)) cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
require.NoError(fileHandler.WriteJSON(constants.StateFilename, tc.existingState, file.OptNone))
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.existingIDFile, file.OptNone))
require.NoError(cmd.Flags().Set("autoscale", strconv.FormatBool(tc.setAutoscaleFlag))) require.NoError(cmd.Flags().Set("autoscale", strconv.FormatBool(tc.setAutoscaleFlag)))
if tc.endpointFlag != "" { if tc.endpointFlag != "" {
require.NoError(cmd.Flags().Set("endpoint", 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 := context.Background()
ctx, cancel := context.WithTimeout(ctx, 4*time.Second) ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
defer cancel() defer cancel()
cmd.SetContext(ctx) 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 { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -510,28 +538,30 @@ func (s *stubInitServer) Init(ctx context.Context, req *initproto.InitRequest) (
return s.initResp, s.initErr 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() t.Helper()
config := config.Default()
config.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab" switch csp {
config.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab" case cloudprovider.Azure:
config.Provider.Azure.Location = "test-location" conf.Provider.Azure.SubscriptionID = "01234567-0123-0123-0123-0123456789ab"
config.Provider.Azure.UserAssignedIdentity = "test-identity" conf.Provider.Azure.TenantID = "01234567-0123-0123-0123-0123456789ab"
config.Provider.Azure.Measurements[8] = []byte("00000000000000000000000000000000") conf.Provider.Azure.Location = "test-location"
config.Provider.Azure.Measurements[9] = []byte("11111111111111111111111111111111") 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" conf.RemoveProviderExcept(csp)
config.Provider.GCP.Project = "test-project" return conf
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
} }
type stubLicenseClient struct{} type stubLicenseClient struct{}

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/config"
"github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/constants"
"github.com/edgelesssys/constellation/internal/crypto/testvector" "github.com/edgelesssys/constellation/internal/crypto/testvector"
"github.com/edgelesssys/constellation/internal/file" "github.com/edgelesssys/constellation/internal/file"
@ -146,7 +147,7 @@ func TestRecover(t *testing.T) {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs) 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.WriteYAML(constants.ConfigFilename, config))
require.NoError(fileHandler.WriteJSON("constellation-mastersecret.json", masterSecret{Key: tc.masterSecret.Secret, Salt: tc.masterSecret.Salt}, file.OptNone)) require.NoError(fileHandler.WriteJSON("constellation-mastersecret.json", masterSecret{Key: tc.masterSecret.Secret, Salt: tc.masterSecret.Salt}, file.OptNone))

View file

@ -12,6 +12,7 @@ import (
"github.com/edgelesssys/constellation/internal/atls" "github.com/edgelesssys/constellation/internal/atls"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/config"
"github.com/edgelesssys/constellation/internal/constants" "github.com/edgelesssys/constellation/internal/constants"
"github.com/edgelesssys/constellation/internal/file" "github.com/edgelesssys/constellation/internal/file"
"github.com/edgelesssys/constellation/internal/grpc/dialer" "github.com/edgelesssys/constellation/internal/grpc/dialer"
@ -190,7 +191,7 @@ func TestVerify(t *testing.T) {
} }
fileHandler := file.NewHandler(tc.setupFs(require)) 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)) require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config))
if tc.idFile != nil { if tc.idFile != nil {
require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.idFile, file.OptNone)) require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.idFile, file.OptNone))

View file

@ -2,15 +2,12 @@ package client
import ( import (
"context" "context"
"time"
"github.com/googleapis/gax-go/v2" "github.com/googleapis/gax-go/v2"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" 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" iampb "google.golang.org/genproto/googleapis/iam/v1"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
) )
type stubOperation struct { type stubOperation struct {
@ -436,54 +433,6 @@ func (a stubInstanceGroupManagersAPI) ListManagedInstances(ctx context.Context,
return a.listIterator 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 { type stubProjectsAPI struct {
getPolicyErr error getPolicyErr error
setPolicyErr error setPolicyErr error

View file

@ -55,7 +55,6 @@ type Client struct {
uid string uid string
zone string zone string
region string region string
serviceAccount string
// loadbalancer // loadbalancer
loadbalancerIP string loadbalancerIP string
@ -270,7 +269,6 @@ func (c *Client) GetState() state.ConstellationState {
GCPFirewalls: c.firewalls, GCPFirewalls: c.firewalls,
GCPNetwork: c.network, GCPNetwork: c.network,
GCPSubnetwork: c.subnetwork, GCPSubnetwork: c.subnetwork,
GCPServiceAccount: c.serviceAccount,
GCPLoadbalancerIPname: c.loadbalancerIPname, GCPLoadbalancerIPname: c.loadbalancerIPname,
} }
for _, lb := range c.loadbalancers { for _, lb := range c.loadbalancers {
@ -297,7 +295,6 @@ func (c *Client) SetState(stat state.ConstellationState) {
c.controlPlaneTemplate = stat.GCPControlPlaneInstanceTemplate c.controlPlaneTemplate = stat.GCPControlPlaneInstanceTemplate
c.loadbalancerIPname = stat.GCPLoadbalancerIPname c.loadbalancerIPname = stat.GCPLoadbalancerIPname
c.loadbalancerIP = stat.LoadBalancerIP c.loadbalancerIP = stat.LoadBalancerIP
c.serviceAccount = stat.GCPServiceAccount
for _, lbName := range stat.GCPLoadbalancers { for _, lbName := range stat.GCPLoadbalancers {
lb := &loadBalancer{ lb := &loadBalancer{
name: lbName, name: lbName,

View file

@ -69,7 +69,6 @@ func TestSetGetState(t *testing.T) {
assert.Equal(state.GCPFirewalls, client.firewalls) assert.Equal(state.GCPFirewalls, client.firewalls)
assert.Equal(state.GCPControlPlaneInstanceTemplate, client.controlPlaneTemplate) assert.Equal(state.GCPControlPlaneInstanceTemplate, client.controlPlaneTemplate)
assert.Equal(state.GCPWorkerInstanceTemplate, client.workerTemplate) assert.Equal(state.GCPWorkerInstanceTemplate, client.workerTemplate)
assert.Equal(state.GCPServiceAccount, client.serviceAccount)
assert.Equal(state.LoadBalancerIP, client.loadbalancerIP) assert.Equal(state.LoadBalancerIP, client.loadbalancerIP)
for _, lb := range client.loadbalancers { for _, lb := range client.loadbalancers {
assert.Contains(state.GCPLoadbalancers, lb.name) assert.Contains(state.GCPLoadbalancers, lb.name)
@ -97,7 +96,6 @@ func TestSetGetState(t *testing.T) {
firewalls: state.GCPFirewalls, firewalls: state.GCPFirewalls,
workerTemplate: state.GCPWorkerInstanceTemplate, workerTemplate: state.GCPWorkerInstanceTemplate,
controlPlaneTemplate: state.GCPControlPlaneInstanceTemplate, controlPlaneTemplate: state.GCPControlPlaneInstanceTemplate,
serviceAccount: state.GCPServiceAccount,
loadbalancerIP: state.LoadBalancerIP, loadbalancerIP: state.LoadBalancerIP,
loadbalancerIPname: state.GCPLoadbalancerIPname, 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) { func TestBuildResourceName(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
clientUID string 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) { func TestCloseAll(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)

View file

@ -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,
}
}

View file

@ -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)
}
})
}
}

View file

@ -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 // 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"` StateDiskType string `yaml:"stateDiskType" validate:"oneof=pd-standard pd-balanced pd-ssd"`
// description: | // description: |
// Roles added to service account. // Path of service account key file. For needed service account roles, see https://constellation-docs.edgeless.systems/constellation/latest/#/getting-started/install?id=authorization
ServiceAccountRoles []string `yaml:"serviceAccountRoles"` ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath"`
// description: | // description: |
// Expected confidential VM measurements. // Expected confidential VM measurements.
Measurements Measurements `yaml:"measurements"` Measurements Measurements `yaml:"measurements"`
@ -232,20 +232,14 @@ func Default() *Config {
EnforcedMeasurements: []uint32{8, 9, 11, 12}, EnforcedMeasurements: []uint32{8, 9, 11, 12},
}, },
GCP: &GCPConfig{ GCP: &GCPConfig{
Project: "", Project: "",
Region: "", Region: "",
Zone: "", Zone: "",
Image: "projects/constellation-images/global/images/constellation-v1-5-0", Image: "projects/constellation-images/global/images/constellation-v1-5-0",
ServiceAccountRoles: []string{ StateDiskType: "pd-ssd",
"roles/compute.instanceAdmin.v1", ServiceAccountKeyPath: "serviceAccountKey.json",
"roles/compute.networkAdmin", Measurements: copyPCRMap(gcpPCRs),
"roles/compute.securityAdmin", EnforcedMeasurements: []uint32{0, 8, 9, 11, 12},
"roles/storage.admin",
"roles/iam.serviceAccountUser",
},
StateDiskType: "pd-ssd",
Measurements: copyPCRMap(gcpPCRs),
EnforcedMeasurements: []uint32{0, 8, 9, 11, 12},
}, },
QEMU: &QEMUConfig{ QEMU: &QEMUConfig{
Measurements: copyPCRMap(qemuPCRs), Measurements: copyPCRMap(qemuPCRs),

View file

@ -245,11 +245,11 @@ func init() {
GCPConfigDoc.Fields[4].Note = "" 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].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[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].Name = "serviceAccountKeyPath"
GCPConfigDoc.Fields[5].Type = "[]string" GCPConfigDoc.Fields[5].Type = "string"
GCPConfigDoc.Fields[5].Note = "" GCPConfigDoc.Fields[5].Note = ""
GCPConfigDoc.Fields[5].Description = "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] = "Roles added to service account." 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].Name = "measurements"
GCPConfigDoc.Fields[6].Type = "Measurements" GCPConfigDoc.Fields[6].Type = "Measurements"
GCPConfigDoc.Fields[6].Note = "" GCPConfigDoc.Fields[6].Note = ""

View file

@ -25,7 +25,6 @@ type ConstellationState struct {
GCPProject string `json:"gcpproject,omitempty"` GCPProject string `json:"gcpproject,omitempty"`
GCPZone string `json:"gcpzone,omitempty"` GCPZone string `json:"gcpzone,omitempty"`
GCPRegion string `json:"gcpregion,omitempty"` GCPRegion string `json:"gcpregion,omitempty"`
GCPServiceAccount string `json:"gcpserviceaccount,omitempty"`
AzureWorkerInstances cloudtypes.Instances `json:"azureworkers,omitempty"` AzureWorkerInstances cloudtypes.Instances `json:"azureworkers,omitempty"`
AzureControlPlaneInstances cloudtypes.Instances `json:"azurecontrolplanes,omitempty"` AzureControlPlaneInstances cloudtypes.Instances `json:"azurecontrolplanes,omitempty"`