diff --git a/cli/cloud/cloudcmd/clients_test.go b/cli/cloud/cloudcmd/clients_test.go index c31f5db16..1d3bbdd07 100644 --- a/cli/cloud/cloudcmd/clients_test.go +++ b/cli/cloud/cloudcmd/clients_test.go @@ -9,6 +9,7 @@ import ( "github.com/edgelesssys/constellation/cli/cloud/cloudtypes" gcpcl "github.com/edgelesssys/constellation/cli/gcp/client" "github.com/edgelesssys/constellation/internal/cloud/cloudprovider" + "github.com/edgelesssys/constellation/internal/gcpshared" "github.com/edgelesssys/constellation/internal/state" ) @@ -307,7 +308,7 @@ func (c *fakeGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateI func (c *fakeGcpClient) CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error) { c.serviceAccount = "service-account@" + c.project + ".iam.gserviceaccount.com" - return gcpcl.ServiceAccountKey{ + return gcpshared.ServiceAccountKey{ Type: "service_account", ProjectID: c.project, PrivateKeyID: "key-id", @@ -318,7 +319,7 @@ func (c *fakeGcpClient) CreateServiceAccount(ctx context.Context, input gcpcl.Se 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", - }.ConvertToCloudServiceAccountURI(), nil + }.ToCloudServiceAccountURI(), nil } func (c *fakeGcpClient) TerminateFirewall(ctx context.Context) error { @@ -398,7 +399,7 @@ func (c *stubGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateI } func (c *stubGcpClient) CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error) { - return gcpcl.ServiceAccountKey{}.ConvertToCloudServiceAccountURI(), c.createServiceAccountErr + return gcpshared.ServiceAccountKey{}.ToCloudServiceAccountURI(), c.createServiceAccountErr } func (c *stubGcpClient) TerminateFirewall(ctx context.Context) error { diff --git a/coordinator/cloudprovider/gcp/ccm.go b/coordinator/cloudprovider/gcp/ccm.go index 0e276b98e..b41bcde9a 100644 --- a/coordinator/cloudprovider/gcp/ccm.go +++ b/coordinator/cloudprovider/gcp/ccm.go @@ -9,6 +9,7 @@ import ( "github.com/edgelesssys/constellation/coordinator/cloudprovider" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" + "github.com/edgelesssys/constellation/internal/gcpshared" k8s "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -80,7 +81,7 @@ func (c *CloudControllerManager) ConfigMaps(instance cloudtypes.Instance) (resou // Secrets returns a list of secrets to deploy together with the k8s cloud-controller-manager. // Reference: https://kubernetes.io/docs/concepts/configuration/secret/ . func (c *CloudControllerManager) Secrets(ctx context.Context, instance cloudtypes.Instance, cloudServiceAccountURI string) (resources.Secrets, error) { - serviceAccountKey, err := getServiceAccountKey(cloudServiceAccountURI) + serviceAccountKey, err := gcpshared.ServiceAccountKeyFromURI(cloudServiceAccountURI) if err != nil { return resources.Secrets{}, err } diff --git a/coordinator/cloudprovider/gcp/ccm_test.go b/coordinator/cloudprovider/gcp/ccm_test.go index 2e13bf298..bce2a0e32 100644 --- a/coordinator/cloudprovider/gcp/ccm_test.go +++ b/coordinator/cloudprovider/gcp/ccm_test.go @@ -5,9 +5,9 @@ import ( "encoding/json" "testing" - "github.com/edgelesssys/constellation/cli/gcp/client" "github.com/edgelesssys/constellation/coordinator/cloudprovider/cloudtypes" "github.com/edgelesssys/constellation/coordinator/kubernetes/k8sapi/resources" + "github.com/edgelesssys/constellation/internal/gcpshared" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" k8s "k8s.io/api/core/v1" @@ -67,7 +67,7 @@ node-tags = constellation-UID } func TestSecrets(t *testing.T) { - serviceAccountKey := client.ServiceAccountKey{ + serviceAccountKey := gcpshared.ServiceAccountKey{ Type: "type", ProjectID: "project-id", PrivateKeyID: "private-key-id", diff --git a/coordinator/cloudprovider/gcp/serviceaccounturi.go b/coordinator/cloudprovider/gcp/serviceaccounturi.go deleted file mode 100644 index dd2a65b22..000000000 --- a/coordinator/cloudprovider/gcp/serviceaccounturi.go +++ /dev/null @@ -1,62 +0,0 @@ -package gcp - -import ( - "fmt" - "net/url" - - "github.com/edgelesssys/constellation/cli/gcp/client" -) - -// getServiceAccountKey converts a cloudServiceAccountURI into a GCP ServiceAccountKey. -func getServiceAccountKey(cloudServiceAccountURI string) (client.ServiceAccountKey, error) { - uri, err := url.Parse(cloudServiceAccountURI) - if err != nil { - return client.ServiceAccountKey{}, err - } - if uri.Scheme != "serviceaccount" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: invalid scheme: %s", uri.Scheme) - } - if uri.Host != "gcp" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: invalid host: %s", uri.Host) - } - query := uri.Query() - if query.Get("type") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"type\": %s", uri) - } - if query.Get("project_id") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"project_id\": %s", uri) - } - if query.Get("private_key_id") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"private_key_id\": %s", uri) - } - if query.Get("private_key") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"private_key\": %s", uri) - } - if query.Get("client_email") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"client_email\": %s", uri) - } - if query.Get("client_id") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"client_id\": %s", uri) - } - if query.Get("token_uri") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"token_uri\": %s", uri) - } - if query.Get("auth_provider_x509_cert_url") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"auth_provider_x509_cert_url\": %s", uri) - } - if query.Get("client_x509_cert_url") == "" { - return client.ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"client_x509_cert_url\": %s", uri) - } - return client.ServiceAccountKey{ - Type: query.Get("type"), - ProjectID: query.Get("project_id"), - PrivateKeyID: query.Get("private_key_id"), - PrivateKey: query.Get("private_key"), - ClientEmail: query.Get("client_email"), - ClientID: query.Get("client_id"), - AuthURI: query.Get("auth_uri"), - TokenURI: query.Get("token_uri"), - AuthProviderX509CertURL: query.Get("auth_provider_x509_cert_url"), - ClientX509CertURL: query.Get("client_x509_cert_url"), - }, nil -} diff --git a/debugd/cdbg/state/state.go b/debugd/cdbg/state/state.go index 658ef55a4..b0aa20930 100644 --- a/debugd/cdbg/state/state.go +++ b/debugd/cdbg/state/state.go @@ -4,7 +4,6 @@ import ( "errors" cmdc "github.com/edgelesssys/constellation/cli/cmd" - "github.com/edgelesssys/constellation/cli/gcp" configc "github.com/edgelesssys/constellation/internal/config" "github.com/edgelesssys/constellation/internal/state" ) @@ -54,7 +53,7 @@ func getAWSInstances(stat state.ConstellationState, _ *configc.Config) (coordina // TODO: make min / max configurable and abstract autoscaling for different cloud providers // TODO: GroupID of nodes is empty, since they currently do not scale. - nodes = cmdc.ScalingGroup{Instances: nodeInstances, GroupID: ""} + nodes = cmdc.ScalingGroup{Instances: nodeInstances} return } @@ -85,10 +84,7 @@ func getGCPInstances(stat state.ConstellationState, config *configc.Config) (coo } // TODO: make min / max configurable and abstract autoscaling for different cloud providers - nodes = cmdc.ScalingGroup{ - Instances: nodeInstances, - GroupID: gcp.AutoscalingNodeGroup(stat.GCPProject, stat.GCPZone, stat.GCPNodeInstanceGroup, config.AutoscalingNodeGroupMin, config.AutoscalingNodeGroupMax), - } + nodes = cmdc.ScalingGroup{Instances: nodeInstances} return } @@ -118,10 +114,7 @@ func getAzureInstances(stat state.ConstellationState, _ *configc.Config) (coordi } // TODO: make min / max configurable and abstract autoscaling for different cloud providers - nodes = cmdc.ScalingGroup{ - Instances: nodeInstances, - GroupID: "", - } + nodes = cmdc.ScalingGroup{Instances: nodeInstances} return } @@ -147,9 +140,6 @@ func getQEMUInstances(stat state.ConstellationState, _ *configc.Config) (coordin for _, node := range nodeMap { nodeInstances = append(nodeInstances, cmdc.Instance(node)) } - nodes = cmdc.ScalingGroup{ - Instances: nodeInstances, - GroupID: "", - } + nodes = cmdc.ScalingGroup{Instances: nodeInstances} return } diff --git a/internal/gcpshared/doc.go b/internal/gcpshared/doc.go new file mode 100644 index 000000000..cc9ce7f8a --- /dev/null +++ b/internal/gcpshared/doc.go @@ -0,0 +1,10 @@ +package gcpshared + +/* +Package gcpshared contains code that is related to Google Cloud Platform +and is used by multiple microservices. + +This package is intended to have a minimal size and surface. If you +have GCP related code that is not shared by multiple microservices, +please keep the code in the microservice's internal package. +*/ diff --git a/internal/gcpshared/serviceaccountkey.go b/internal/gcpshared/serviceaccountkey.go new file mode 100644 index 000000000..492be9097 --- /dev/null +++ b/internal/gcpshared/serviceaccountkey.go @@ -0,0 +1,94 @@ +package gcpshared + +import ( + "fmt" + "net/url" +) + +// ServiceAccountKey is a GCP service account key. +type ServiceAccountKey struct { + Type string `json:"type"` + ProjectID string `json:"project_id"` + PrivateKeyID string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + ClientEmail string `json:"client_email"` + ClientID string `json:"client_id"` + AuthURI string `json:"auth_uri"` + TokenURI string `json:"token_uri"` + AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"` + ClientX509CertURL string `json:"client_x509_cert_url"` +} + +func ServiceAccountKeyFromURI(serviceAccountURI string) (ServiceAccountKey, error) { + uri, err := url.Parse(serviceAccountURI) + if err != nil { + return ServiceAccountKey{}, err + } + if uri.Scheme != "serviceaccount" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: invalid scheme: %s", uri.Scheme) + } + if uri.Host != "gcp" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: invalid host: %s", uri.Host) + } + query := uri.Query() + if query.Get("type") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"type\": %s", uri) + } + if query.Get("project_id") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"project_id\": %s", uri) + } + if query.Get("private_key_id") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"private_key_id\": %s", uri) + } + if query.Get("private_key") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"private_key\": %s", uri) + } + if query.Get("client_email") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"client_email\": %s", uri) + } + if query.Get("client_id") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"client_id\": %s", uri) + } + if query.Get("token_uri") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"token_uri\": %s", uri) + } + if query.Get("auth_provider_x509_cert_url") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"auth_provider_x509_cert_url\": %s", uri) + } + if query.Get("client_x509_cert_url") == "" { + return ServiceAccountKey{}, fmt.Errorf("invalid service account URI: missing parameter \"client_x509_cert_url\": %s", uri) + } + return ServiceAccountKey{ + Type: query.Get("type"), + ProjectID: query.Get("project_id"), + PrivateKeyID: query.Get("private_key_id"), + PrivateKey: query.Get("private_key"), + ClientEmail: query.Get("client_email"), + ClientID: query.Get("client_id"), + AuthURI: query.Get("auth_uri"), + TokenURI: query.Get("token_uri"), + AuthProviderX509CertURL: query.Get("auth_provider_x509_cert_url"), + ClientX509CertURL: query.Get("client_x509_cert_url"), + }, nil +} + +// ToCloudServiceAccountURI converts the ServiceAccountKey into a cloud service account URI. +func (k ServiceAccountKey) ToCloudServiceAccountURI() string { + query := url.Values{} + query.Add("type", k.Type) + query.Add("project_id", k.ProjectID) + query.Add("private_key_id", k.PrivateKeyID) + query.Add("private_key", k.PrivateKey) + query.Add("client_email", k.ClientEmail) + query.Add("client_id", k.ClientID) + query.Add("auth_uri", k.AuthURI) + query.Add("token_uri", k.TokenURI) + query.Add("auth_provider_x509_cert_url", k.AuthProviderX509CertURL) + query.Add("client_x509_cert_url", k.ClientX509CertURL) + uri := url.URL{ + Scheme: "serviceaccount", + Host: "gcp", + RawQuery: query.Encode(), + } + return uri.String() +} diff --git a/coordinator/cloudprovider/gcp/serviceaccounturi_test.go b/internal/gcpshared/serviceaccountkey_test.go similarity index 74% rename from coordinator/cloudprovider/gcp/serviceaccounturi_test.go rename to internal/gcpshared/serviceaccountkey_test.go index 8386fc595..da81849e4 100644 --- a/coordinator/cloudprovider/gcp/serviceaccounturi_test.go +++ b/internal/gcpshared/serviceaccountkey_test.go @@ -1,15 +1,15 @@ -package gcp +package gcpshared import ( + "net/url" "testing" - "github.com/edgelesssys/constellation/cli/gcp/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestGetServiceAccountKey(t *testing.T) { - serviceAccountKey := client.ServiceAccountKey{ +func TestServiceAccountKeyFromURI(t *testing.T) { + serviceAccountKey := ServiceAccountKey{ Type: "type", ProjectID: "project-id", PrivateKeyID: "private-key-id", @@ -23,10 +23,10 @@ func TestGetServiceAccountKey(t *testing.T) { } testCases := map[string]struct { cloudServiceAccountURI string - wantKey client.ServiceAccountKey + wantKey ServiceAccountKey wantErr bool }{ - "getServiceAccountKey works": { + "successful": { cloudServiceAccountURI: "serviceaccount://gcp?type=type&project_id=project-id&private_key_id=private-key-id&private_key=private-key&client_email=client-email&client_id=client-id&auth_uri=auth-uri&token_uri=token-uri&auth_provider_x509_cert_url=auth-provider-x509-cert-url&client_x509_cert_url=client-x509-cert-url", wantKey: serviceAccountKey, }, @@ -85,7 +85,7 @@ func TestGetServiceAccountKey(t *testing.T) { assert := assert.New(t) require := require.New(t) - key, err := getServiceAccountKey(tc.cloudServiceAccountURI) + key, err := ServiceAccountKeyFromURI(tc.cloudServiceAccountURI) if tc.wantErr { assert.Error(err) return @@ -95,3 +95,38 @@ func TestGetServiceAccountKey(t *testing.T) { }) } } + +func TestConvertToCloudServiceAccountURI(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + key := 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", + } + cloudServiceAccountURI := key.ToCloudServiceAccountURI() + uri, err := url.Parse(cloudServiceAccountURI) + require.NoError(err) + query := uri.Query() + assert.Equal("serviceaccount", uri.Scheme) + assert.Equal("gcp", uri.Host) + assert.Equal(url.Values{ + "type": []string{"type"}, + "project_id": []string{"project-id"}, + "private_key_id": []string{"private-key-id"}, + "private_key": []string{"private-key"}, + "client_email": []string{"client-email"}, + "client_id": []string{"client-id"}, + "auth_uri": []string{"auth-uri"}, + "token_uri": []string{"token-uri"}, + "auth_provider_x509_cert_url": []string{"auth-provider-x509-cert-url"}, + "client_x509_cert_url": []string{"client-x509-cert-url"}, + }, query) +}