From e9fe6e8fc6119051658319ba56527b9f1a1fb751 Mon Sep 17 00:00:00 2001 From: Fabian Fulga Date: Mon, 23 Sep 2024 13:58:39 +0300 Subject: [PATCH] Add ServiceAccount as extraspecs --- README.md | 10 ++++++++++ internal/client/gcp.go | 1 + internal/spec/spec.go | 28 +++++++++++++++----------- internal/spec/spec_test.go | 40 ++++++++++++++++++++++++++++++-------- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 05fa7c0..9962552 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,13 @@ To this end, this provider supports the following extra specs schema: "type": "string" } }, + "service_accounts": { + "type": "array", + "description": "A list of service accounts to be attached to the instance", + "items": { + "$ref": "#/$defs/ServiceAccount" + } + }, "source_snapshot": { "type": "string", "description": "The source snapshot to create this disk." @@ -185,11 +192,14 @@ An example of extra specs json would look like this: "nic_type": "VIRTIO_NET", "custom_labels": {"environment":"production","project":"myproject"}, "network_tags": ["web-server", "production"], + "service_accounts": [{"email":"email@email.com", "scopes":["https://www.googleapis.com/auth/devstorage.read_only", "https://www.googleapis.com/auth/logging.write"]}], "source_snapshot": "projects/garm-testing/global/snapshots/garm-snapshot", "ssh_keys": ["username1:ssh_key1", "username2:ssh_key2"] } ``` +**NOTE**: Using the `service_accounts` extra specs when creating instances **introduces certain risks that must be carefully managed**. **Service accounts** grant access to specific resources, and if improperly configured, they can expose sensitive data or allow unauthorized actions. Misconfigured permissions or overly broad scopes can lead to privilege escalation, enabling attackers or unintended users to access critical resources. It's essential to follow the principle of least privilege, ensuring that service accounts only have the necessary permissions for their intended tasks. Regular audits and proper key management are also crucial to safeguard access and prevent potential security vulnerabilities. + **NOTE**: The `custom_labels` and `network_tags` must meet the [GCP requirements for labels](https://cloud.google.com/compute/docs/labeling-resources#requirements) and the [GCP requirements for network tags](https://cloud.google.com/vpc/docs/add-remove-network-tags#restrictions)! **NOTE**: The `ssh_keys` add the option to [connect to an instance via SSH](https://cloud.google.com/compute/docs/instances/ssh) (either Linux or Windows). After you added the key as `username:ssh_public_key`, you can use the `private_key` to connect to the Linux/Windows instance via `ssh -i private_rsa username@instance_ip`. For **Windows** instances, the provider installs on the instance `google-compute-engine-ssh` and `enables ssh` if a `ssh_key` is added to extra-specs. diff --git a/internal/client/gcp.go b/internal/client/gcp.go index ee3571c..4201b09 100644 --- a/internal/client/gcp.go +++ b/internal/client/gcp.go @@ -172,6 +172,7 @@ func (g *GcpCli) CreateInstance(ctx context.Context, spec *spec.RunnerSpec) (*co Tags: &computepb.Tags{ Items: spec.NetworkTags, }, + ServiceAccounts: spec.ServiceAccounts, } if !g.cfg.ExternalIPAccess { diff --git a/internal/spec/spec.go b/internal/spec/spec.go index 4445b83..80384b2 100644 --- a/internal/spec/spec.go +++ b/internal/spec/spec.go @@ -21,6 +21,7 @@ import ( "maps" "regexp" + "cloud.google.com/go/compute/apiv1/computepb" "github.com/cloudbase/garm-provider-common/cloudconfig" "github.com/cloudbase/garm-provider-common/params" "github.com/cloudbase/garm-provider-common/util" @@ -127,17 +128,18 @@ func (e *extraSpecs) Validate() error { } type extraSpecs struct { - DiskSize int64 `json:"disksize,omitempty" jsonschema:"description=The size of the root disk in GB. Default is 127 GB."` - DiskType string `json:"disktype,omitempty" jsonschema:"description=The type of the disk. Default is pd-standard."` - DisplayDevice bool `json:"display_device,omitempty" jsonschema:"description=Enable the display device on the VM."` - NetworkID string `json:"network_id,omitempty" jsonschema:"description=The name of the network attached to the instance."` - SubnetworkID string `json:"subnetwork_id,omitempty" jsonschema:"description=The name of the subnetwork attached to the instance."` - NicType string `json:"nic_type,omitempty" jsonschema:"description=The type of the network interface card. Default is VIRTIO_NET."` - CustomLabels map[string]string `json:"custom_labels,omitempty" jsonschema:"description=Custom labels to apply to the instance. Each label is a key-value pair where both key and value are strings."` - NetworkTags []string `json:"network_tags,omitempty" jsonschema:"description=A list of network tags to be attached to the instance"` - SourceSnapshot string `json:"source_snapshot,omitempty" jsonschema:"description=The source snapshot to create this disk."` - SSHKeys []string `json:"ssh_keys,omitempty" jsonschema:"description=A list of SSH keys to be added to the instance. The format is USERNAME:SSH_KEY"` - EnableBootDebug *bool `json:"enable_boot_debug,omitempty" jsonschema:"description=Enable boot debug on the VM."` + DiskSize int64 `json:"disksize,omitempty" jsonschema:"description=The size of the root disk in GB. Default is 127 GB."` + DiskType string `json:"disktype,omitempty" jsonschema:"description=The type of the disk. Default is pd-standard."` + DisplayDevice bool `json:"display_device,omitempty" jsonschema:"description=Enable the display device on the VM."` + NetworkID string `json:"network_id,omitempty" jsonschema:"description=The name of the network attached to the instance."` + SubnetworkID string `json:"subnetwork_id,omitempty" jsonschema:"description=The name of the subnetwork attached to the instance."` + NicType string `json:"nic_type,omitempty" jsonschema:"description=The type of the network interface card. Default is VIRTIO_NET."` + CustomLabels map[string]string `json:"custom_labels,omitempty" jsonschema:"description=Custom labels to apply to the instance. Each label is a key-value pair where both key and value are strings."` + NetworkTags []string `json:"network_tags,omitempty" jsonschema:"description=A list of network tags to be attached to the instance"` + ServiceAccounts []*computepb.ServiceAccount `json:"service_accounts,omitempty" jsonschema:"description=A list of service accounts to be attached to the instance"` + SourceSnapshot string `json:"source_snapshot,omitempty" jsonschema:"description=The source snapshot to create this disk."` + SSHKeys []string `json:"ssh_keys,omitempty" jsonschema:"description=A list of SSH keys to be added to the instance. The format is USERNAME:SSH_KEY"` + EnableBootDebug *bool `json:"enable_boot_debug,omitempty" jsonschema:"description=Enable boot debug on the VM."` // The Cloudconfig struct from common package cloudconfig.CloudConfigSpec } @@ -189,6 +191,7 @@ type RunnerSpec struct { DiskType string CustomLabels map[string]string NetworkTags []string + ServiceAccounts []*computepb.ServiceAccount SourceSnapshot string SSHKeys string EnableBootDebug bool @@ -219,6 +222,9 @@ func (r *RunnerSpec) MergeExtraSpecs(extraSpecs *extraSpecs) { if len(extraSpecs.NetworkTags) > 0 { r.NetworkTags = extraSpecs.NetworkTags } + if len(extraSpecs.ServiceAccounts) > 0 { + r.ServiceAccounts = extraSpecs.ServiceAccounts + } if extraSpecs.SourceSnapshot != "" { r.SourceSnapshot = extraSpecs.SourceSnapshot } diff --git a/internal/spec/spec_test.go b/internal/spec/spec_test.go index 5f70158..6d3cc71 100644 --- a/internal/spec/spec_test.go +++ b/internal/spec/spec_test.go @@ -20,8 +20,10 @@ import ( "fmt" "testing" + "cloud.google.com/go/compute/apiv1/computepb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) func TestJsonSchemaValidation(t *testing.T) { @@ -43,6 +45,8 @@ func TestJsonSchemaValidation(t *testing.T) { "example_label": "example_value" }, "network_tags": ["example_tag"], + "service_accounts": [{"email": "email", "scopes": ["scope"]}], + "service_accounts": [{"email": "email", "scopes": ["scope", "scope2"]}, {"email": "email2", "scopes": ["scope2"]}], "source_snapshot": "snapshot-id", "ssh_keys": ["ssh-key", "ssh-key2"], "enable_boot_debug": true, @@ -108,6 +112,13 @@ func TestJsonSchemaValidation(t *testing.T) { }`), errString: "", }, + { + name: "Specs just with service_accounts", + input: json.RawMessage(`{ + "service_accounts": [{"email": "email", "scopes": ["scope"]}] + }`), + errString: "", + }, { name: "Specs just with source_snapshot", input: json.RawMessage(`{ @@ -196,6 +207,13 @@ func TestJsonSchemaValidation(t *testing.T) { }`), errString: "schema validation failed: [network_tags: Invalid type. Expected: array, given: string]", }, + { + name: "Invalid input for service_accounts - wrong data type", + input: json.RawMessage(`{ + "service_accounts": "email" + }`), + errString: "schema validation failed: [service_accounts: Invalid type. Expected: array, given: string]", + }, { name: "Invalid input for ssh_keys - wrong data type", input: json.RawMessage(`{ @@ -273,14 +291,20 @@ func TestMergeExtraSpecs(t *testing.T) { { name: "ValidExtraSpecs", extraSpecs: &extraSpecs{ - NetworkID: "projects/garm-testing/global/networks/garm-2", - SubnetworkID: "projects/garm-testing/regions/europe-west1/subnetworks/garm", - DisplayDevice: true, - DiskSize: 100, - DiskType: "projects/garm-testing/zones/europe-west1/diskTypes/pd-ssd", - NicType: "VIRTIO_NET", - CustomLabels: map[string]string{"key1": "value1"}, - NetworkTags: []string{"tag1", "tag2"}, + NetworkID: "projects/garm-testing/global/networks/garm-2", + SubnetworkID: "projects/garm-testing/regions/europe-west1/subnetworks/garm", + DisplayDevice: true, + DiskSize: 100, + DiskType: "projects/garm-testing/zones/europe-west1/diskTypes/pd-ssd", + NicType: "VIRTIO_NET", + CustomLabels: map[string]string{"key1": "value1"}, + NetworkTags: []string{"tag1", "tag2"}, + ServiceAccounts: []*computepb.ServiceAccount{ + { + Email: proto.String("email"), + Scopes: []string{"scope"}, + }, + }, SourceSnapshot: "projects/garm-testing/global/snapshots/garm-snapshot", SSHKeys: []string{"ssh-key1", "ssh-key2"}, EnableBootDebug: &enable_boot_debug,