Skip to content

Commit

Permalink
Merge pull request #11 from gardener/enh/credentials
Browse files Browse the repository at this point in the history
Support Gardener credentials data keys
  • Loading branch information
prashanth26 authored Dec 16, 2020
2 parents 4ef11e6 + 71d8bfa commit f5df365
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 40 deletions.
4 changes: 3 additions & 1 deletion kubernetes/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ metadata:
data:
userData: "encoded-cloud-config" # GCP cloud config file (base64 encoded)
serviceAccountJSON: "{...}" # GCP service account json object (base64 encoded)
type: Opaque
### Alternative data keys are:
# serviceaccount.json: "{...}" # GCP service account json object (base64 encoded)
type: Opaque
4 changes: 4 additions & 0 deletions pkg/gcp/apis/provider_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ package api
const (
// GCPServiceAccountJSON is a constant for a key name that is part of the GCP cloud credentials.
GCPServiceAccountJSON = "serviceAccountJSON"
// GCPAlternativeServiceAccountJSON is a constant for a key name of a secret containing the GCP credentials (service
// account json).
GCPAlternativeServiceAccountJSON = "serviceaccount.json"

// APIVersionV1alpha1 is the API version for MCM
APIVersionV1alpha1 = "mcm.gardener.cloud/v1alpha1"
)
Expand Down
11 changes: 6 additions & 5 deletions pkg/gcp/apis/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@ func validateSecrets(secret *corev1.Secret) []error {
var allErrs []error

if secret == nil {
allErrs = append(allErrs, fmt.Errorf("Secret object that has been passed by the MCM is nil"))
allErrs = append(allErrs, fmt.Errorf("secret object that has been passed by the MCM is nil"))
} else {
_, serviceAccountJSONExists := secret.Data["serviceAccountJSON"]
_, serviceAccountJSONExists := secret.Data[api.GCPServiceAccountJSON]
_, serviceAccountJSONAlternativeExists := secret.Data[api.GCPAlternativeServiceAccountJSON]
_, userDataExists := secret.Data["userData"]

if !serviceAccountJSONExists {
allErrs = append(allErrs, fmt.Errorf("Secret serviceAccountJSON is required field"))
if !serviceAccountJSONExists && !serviceAccountJSONAlternativeExists {
allErrs = append(allErrs, fmt.Errorf("secret %s or %s is required field", api.GCPServiceAccountJSON, api.GCPAlternativeServiceAccountJSON))
}
if !userDataExists {
allErrs = append(allErrs, fmt.Errorf("Secret userData is required field"))
allErrs = append(allErrs, fmt.Errorf("secret userData is required field"))
}
}

Expand Down
12 changes: 8 additions & 4 deletions pkg/gcp/fake/mockclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,26 @@ import (
compute "google.golang.org/api/compute/v1"
option "google.golang.org/api/option"
corev1 "k8s.io/api/core/v1"

api "github.com/gardener/machine-controller-manager-provider-gcp/pkg/gcp/apis"
)

//PluginSPIImpl is the mock implementation of PluginSPIImpl
// PluginSPIImpl is the mock implementation of PluginSPIImpl
type PluginSPIImpl struct {
Client *http.Client
}

//NewComputeService creates a compute service instance using the mock
// NewComputeService creates a compute service instance using the mock
func (ms *PluginSPIImpl) NewComputeService(secrets *corev1.Secret) (context.Context, *compute.Service, error) {
ctx := context.Background()

if _, exists := secrets.Data["serviceAccountJSON"]; !exists {
_, serviceAccountJSON := secrets.Data[api.GCPServiceAccountJSON]
_, serviceAccountJSONAlternative := secrets.Data[api.GCPAlternativeServiceAccountJSON]
if !serviceAccountJSON && !serviceAccountJSONAlternative {
return nil, nil, fmt.Errorf("Missing secrets to connect to compute service")
}

//create a compute service using a mockclient work
// create a compute service using a mockclient work
client := option.WithHTTPClient(ms.Client)
endpoint := option.WithEndpoint("http://127.0.0.1:6666")

Expand Down
15 changes: 8 additions & 7 deletions pkg/gcp/machine_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"net/http"

api "github.com/gardener/machine-controller-manager-provider-gcp/pkg/gcp/apis"
fake "github.com/gardener/machine-controller-manager-provider-gcp/pkg/gcp/fake"
v1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1"
"github.com/gardener/machine-controller-manager/pkg/util/provider/driver"
Expand Down Expand Up @@ -48,9 +49,9 @@ const (
// ListFailAtJSONUnmarshalling is the error message returned when an malformed JSON is sent to the plugin by the caller
ListFailAtJSONUnmarshalling string = "machine codes error: code = [Internal] message = [List machines failed on decodeProviderSpecAndSecret: machine codes error: code = [Internal] message = [unexpected end of JSON input]]"
// FailAtNoSecretsPassed is the error message returned when no secrets are passed to the the plugin by the caller
FailAtNoSecretsPassed string = "machine codes error: code = [Internal] message = [Create machine \"dummy-machine\" failed on decodeProviderSpecAndSecret: machine codes error: code = [Internal] message = [Error while validating ProviderSpec [Secret serviceAccountJSON is required field Secret userData is required field]]]"
FailAtNoSecretsPassed string = "machine codes error: code = [Internal] message = [Create machine \"dummy-machine\" failed on decodeProviderSpecAndSecret: machine codes error: code = [Internal] message = [Error while validating ProviderSpec [secret serviceAccountJSON or serviceaccount.json is required field secret userData is required field]]]"
// FailAtSecretsWithNoUserData is the error message returned when secrets map has no userdata provided by the caller
FailAtSecretsWithNoUserData string = "machine codes error: code = [Internal] message = [Create machine \"dummy-machine\" failed on decodeProviderSpecAndSecret: machine codes error: code = [Internal] message = [Error while validating ProviderSpec [Secret userData is required field]]]"
FailAtSecretsWithNoUserData string = "machine codes error: code = [Internal] message = [Create machine \"dummy-machine\" failed on decodeProviderSpecAndSecret: machine codes error: code = [Internal] message = [Error while validating ProviderSpec [secret userData is required field]]]"
// FailAtInvalidProjectID is the error returned when an invalid project id value is provided by the caller
FailAtInvalidProjectID string = "machine codes error: code = [Internal] message = [Create machine \"dummy-machine\" failed: json: cannot unmarshal number into Go struct field .project_id of type string]"
// FailAtInvalidZonePostCall is the error returned when a post call should fail with an invalid zone is sent in the POST call -- this is used to simulate server error
Expand Down Expand Up @@ -114,17 +115,17 @@ var _ = Describe("#MachineController", func() {
}

gcpProviderSecret := map[string][]byte{
"userData": []byte("dummy-data"),
"serviceAccountJSON": []byte("{\"type\":\"service_account\",\"project_id\":\"sap-se-gcp-scp-k8s-dev\"}"),
"userData": []byte("dummy-data"),
api.GCPServiceAccountJSON: []byte("{\"type\":\"service_account\",\"project_id\":\"sap-se-gcp-scp-k8s-dev\"}"),
}

gcpProviderSecretWithMisssingUserData := map[string][]byte{
//"userData": []byte(""),
"serviceAccountJSON": []byte("{\"type\":\"service_account\",\"project_id\":\"sap-se-gcp-scp-k8s-dev\"}"),
api.GCPServiceAccountJSON: []byte("{\"type\":\"service_account\",\"project_id\":\"sap-se-gcp-scp-k8s-dev\"}"),
}
gcpProviderSecretWithoutProjectID := map[string][]byte{
"userData": []byte("dummy-data"),
"serviceAccountJSON": []byte("{\"type\":\"service_account\",\"project_id\":10}"),
"userData": []byte("dummy-data"),
api.GCPServiceAccountJSON: []byte("{\"type\":\"service_account\",\"project_id\":10}"),
}

var ms *MachinePlugin
Expand Down
40 changes: 21 additions & 19 deletions pkg/gcp/machine_controller_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ const (
)

// CreateMachineUtil method is used to create a GCP machine
func (ms *MachinePlugin) CreateMachineUtil(ctx context.Context, machineName string, providerSpec *api.GCPProviderSpec, secrets *corev1.Secret) (string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secrets)
func (ms *MachinePlugin) CreateMachineUtil(ctx context.Context, machineName string, providerSpec *api.GCPProviderSpec, secret *corev1.Secret) (string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secret)
if err != nil {
return "", err
}

project, err := extractProject(secrets.Data[api.GCPServiceAccountJSON])
project, err := extractProject(secret.Data)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -118,7 +118,7 @@ func (ms *MachinePlugin) CreateMachineUtil(ctx context.Context, machineName stri
instance.Disks = disks

var metadataItems = []*compute.MetadataItems{}
metadataItems = append(metadataItems, getUserData(string(secrets.Data["userData"])))
metadataItems = append(metadataItems, getUserData(string(secret.Data["userData"])))

for _, metadata := range providerSpec.Metadata {
metadataItems = append(metadataItems, &compute.MetadataItems{
Expand Down Expand Up @@ -191,20 +191,20 @@ func decodeMachineID(id string) (string, string, string, error) {
}

// DeleteMachineUtil deletes a VM by name
func (ms *MachinePlugin) DeleteMachineUtil(ctx context.Context, machineName string, providerID string, providerSpec *api.GCPProviderSpec, secrets *corev1.Secret) (string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secrets)
func (ms *MachinePlugin) DeleteMachineUtil(ctx context.Context, machineName string, _ string, providerSpec *api.GCPProviderSpec, secret *corev1.Secret) (string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secret)
if err != nil {
return "", err
}

project, err := extractProject(secrets.Data[api.GCPServiceAccountJSON])
project, err := extractProject(secret.Data)
if err != nil {
return "", err
}

zone := providerSpec.Zone

result, err := getVMs(ctx, machineName, providerSpec, secrets, project, computeService)
result, err := getVMs(ctx, machineName, providerSpec, secret, project, computeService)
if err != nil {
return "", err
} else if len(result) == 0 {
Expand All @@ -223,19 +223,19 @@ func (ms *MachinePlugin) DeleteMachineUtil(ctx context.Context, machineName stri
}

// GetMachineStatusUtil checks for existence of VM by name
func (ms *MachinePlugin) GetMachineStatusUtil(ctx context.Context, machineName string, providerID string, providerSpec *api.GCPProviderSpec, secrets *corev1.Secret) (string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secrets)
func (ms *MachinePlugin) GetMachineStatusUtil(ctx context.Context, machineName string, _ string, providerSpec *api.GCPProviderSpec, secret *corev1.Secret) (string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secret)
if err != nil {
return "", err
}

project, err := extractProject(secrets.Data[api.GCPServiceAccountJSON])
project, err := extractProject(secret.Data)
if err != nil {
return "", err
}
zone := providerSpec.Zone

result, err := getVMs(ctx, machineName, providerSpec, secrets, project, computeService)
result, err := getVMs(ctx, machineName, providerSpec, secret, project, computeService)
if err != nil {
return "", err
} else if len(result) == 0 {
Expand All @@ -247,18 +247,18 @@ func (ms *MachinePlugin) GetMachineStatusUtil(ctx context.Context, machineName s
}

// ListMachinesUtil lists all VMs in the DC or folder
func (ms *MachinePlugin) ListMachinesUtil(ctx context.Context, providerSpec *api.GCPProviderSpec, secrets *corev1.Secret) (map[string]string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secrets)
func (ms *MachinePlugin) ListMachinesUtil(ctx context.Context, providerSpec *api.GCPProviderSpec, secret *corev1.Secret) (map[string]string, error) {
ctx, computeService, err := ms.SPI.NewComputeService(secret)
if err != nil {
return nil, err
}

project, err := extractProject(secrets.Data[api.GCPServiceAccountJSON])
project, err := extractProject(secret.Data)
if err != nil {
return nil, err
}

result, err := getVMs(ctx, "", providerSpec, secrets, project, computeService)
result, err := getVMs(ctx, "", providerSpec, secret, project, computeService)
if err != nil {
return nil, err
} else if len(result) == 0 {
Expand All @@ -269,7 +269,7 @@ func (ms *MachinePlugin) ListMachinesUtil(ctx context.Context, providerSpec *api
return result, nil
}

func getVMs(ctx context.Context, machineID string, providerSpec *api.GCPProviderSpec, secrets *corev1.Secret, project string, computeService *compute.Service) (map[string]string, error) {
func getVMs(ctx context.Context, machineID string, providerSpec *api.GCPProviderSpec, _ *corev1.Secret, project string, computeService *compute.Service) (map[string]string, error) {
listOfVMs := make(map[string]string)

searchClusterName := ""
Expand Down Expand Up @@ -367,11 +367,13 @@ func prepareErrorf(err error, format string, args ...interface{}) error {
return status.Error(code, wrapped.Error())
}

func extractProject(serviceaccount []byte) (string, error) {
func extractProject(credentialsData map[string][]byte) (string, error) {
serviceAccountJSON := extractCredentialsFromData(credentialsData, api.GCPServiceAccountJSON, api.GCPAlternativeServiceAccountJSON)

var j struct {
Project string `json:"project_id"`
}
if err := json.Unmarshal(serviceaccount, &j); err != nil {
if err := json.Unmarshal([]byte(serviceAccountJSON), &j); err != nil {
return "Error", err
}
return j.Project, nil
Expand Down
23 changes: 19 additions & 4 deletions pkg/gcp/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ package gcp

import (
"context"
"strings"

api "github.com/gardener/machine-controller-manager-provider-gcp/pkg/gcp/apis"
"golang.org/x/oauth2/google"
compute "google.golang.org/api/compute/v1"
"google.golang.org/api/compute/v1"
"google.golang.org/api/option"
corev1 "k8s.io/api/core/v1"

api "github.com/gardener/machine-controller-manager-provider-gcp/pkg/gcp/apis"
)

// PluginSPI provides an interface to deal with cloud provider session
Expand All @@ -49,9 +51,11 @@ type MachinePlugin struct {
type PluginSPIImpl struct{}

// NewComputeService returns an instance of the compute service
func (spi *PluginSPIImpl) NewComputeService(secrets *corev1.Secret) (context.Context, *compute.Service, error) {
func (spi *PluginSPIImpl) NewComputeService(secret *corev1.Secret) (context.Context, *compute.Service, error) {
ctx := context.Background()
jwt, err := google.JWTConfigFromJSON((secrets.Data[api.GCPServiceAccountJSON]), compute.CloudPlatformScope)
serviceAccountJSON := extractCredentialsFromData(secret.Data, api.GCPServiceAccountJSON, api.GCPAlternativeServiceAccountJSON)

jwt, err := google.JWTConfigFromJSON([]byte(serviceAccountJSON), compute.CloudPlatformScope)
if err != nil {
return nil, nil, err
}
Expand All @@ -71,3 +75,14 @@ func NewGCPPlugin(pluginSPI PluginSPI) *MachinePlugin {
SPI: pluginSPI,
}
}

// extractCredentialsFromData extracts and trims a value from the given data map. The first key that exists is being
// returned, otherwise, the next key is tried, etc. If no key exists then an empty string is returned.
func extractCredentialsFromData(data map[string][]byte, keys ...string) string {
for _, key := range keys {
if val, ok := data[key]; ok {
return strings.TrimSpace(string(val))
}
}
return ""
}

0 comments on commit f5df365

Please sign in to comment.