diff --git a/src/files.go b/src/files.go index 11311d29..63bf8cfc 100644 --- a/src/files.go +++ b/src/files.go @@ -387,6 +387,3 @@ var awsAcmCertificate []byte //go:embed mapping/aws/resource/acm-pa/aws_acmpca_certificate_authority.json var awsAcmpcaCertificateAuthority []byte - -//go:embed mapping/gcp/google_compute_instance.json -var googleComputeInstance []byte diff --git a/src/files_gcp.go b/src/files_gcp.go new file mode 100644 index 00000000..219843a8 --- /dev/null +++ b/src/files_gcp.go @@ -0,0 +1,8 @@ +package pike + +import ( + _ "embed" // required for embed +) + +//go:embed mapping/gcp/resource/google_compute_instance.json +var googleComputeInstance []byte diff --git a/src/gcp.go b/src/gcp.go index 3397f016..32912590 100644 --- a/src/gcp.go +++ b/src/gcp.go @@ -7,12 +7,28 @@ import ( // GetGCPPermissions for GCP resources func GetGCPPermissions(result ResourceV2) []string { var Permissions []string - switch result.Name { - case "googleComputeInstance": - Permissions = GetPermissionMap(googleComputeInstance, result.Attributes) + if result.TypeName == "resource" { + Permissions = GetGCPResourcePermissions(result) + } else { + Permissions = GetGCPDataPermissions(result) + } + + return Permissions +} + +// GetGCPResourcePermissions looks up permissions required for resources +func GetGCPResourcePermissions(result ResourceV2) []string { + TFLookup := map[string]interface{}{ + "google_compute_instance": googleComputeInstance, + } + + var Permissions []string - default: - log.Printf("%s not yet implemented", result.Name) + temp := TFLookup[result.Name] + if temp != nil { + Permissions = GetPermissionMap(TFLookup[result.Name].([]byte), result.Attributes) + } else { + log.Printf("%s not implemented", result.Name) } return Permissions diff --git a/src/gcp_data_source.go b/src/gcp_data_source.go new file mode 100644 index 00000000..aee95e86 --- /dev/null +++ b/src/gcp_data_source.go @@ -0,0 +1,20 @@ +package pike + +import "log" + +// GetGCPDataPermissions gets permissions required for datasources +func GetGCPDataPermissions(result ResourceV2) []string { + + TFLookup := map[string]interface{}{} + + var Permissions []string + + temp := TFLookup[result.Name] + if temp != nil { + Permissions = GetPermissionMap(TFLookup[result.Name].([]byte), result.Attributes) + } else { + log.Printf("data.%s not implemented", result.Name) + } + + return Permissions +} diff --git a/src/gcp_policy.go b/src/gcp_policy.go new file mode 100644 index 00000000..c3091072 --- /dev/null +++ b/src/gcp_policy.go @@ -0,0 +1,39 @@ +package pike + +import ( + "bytes" + _ "embed" //required for embed + "strings" + "text/template" +) + +//go:embed terraform.gcppolicy.template +var policyGCPTemplate []byte + +// GCPPolicy create an IAM policy +func GCPPolicy(permissions []string) (string, error) { + test := strings.Join(permissions, "\", \n\t \"") + + type GCPPolicyDetails struct { + Name string + Project string + RoleID string + Permissions string + } + + PolicyName := "terraform" + randSeq(8) + theDetails := GCPPolicyDetails{PolicyName, "examplea", "terraform_pike", test} + + var output bytes.Buffer + tmpl, err := template.New("test").Parse(string(policyGCPTemplate)) + if err != nil { + panic(err) + } + + err = tmpl.Execute(&output, theDetails) + + if err != nil { + panic(err) + } + return output.String(), nil +} diff --git a/src/mapping/gcp/google_compute_instance.json b/src/mapping/gcp/google_compute_instance.json deleted file mode 100644 index eb2c264a..00000000 --- a/src/mapping/gcp/google_compute_instance.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "apply": [ - "compute.images.get" - ], - "attributes": { - "tags": [] - }, - "destroy": [], - "modify": [], - "plan": [] - } -] diff --git a/src/mapping/gcp/resource/google_compute_instance.json b/src/mapping/gcp/resource/google_compute_instance.json new file mode 100644 index 00000000..111ca465 --- /dev/null +++ b/src/mapping/gcp/resource/google_compute_instance.json @@ -0,0 +1,25 @@ +[ + { + "apply": [ + "compute.zones.get", + "compute.instances.create", + "compute.instances.get", + "compute.disks.create", + "compute.disks.create", + "compute.subnetworks.use", + "compute.subnetworks.useExternalIp", + "compute.instances.setMetadata", + "compute.instances.delete" + ], + "attributes": { + "tags": [ + "compute.instances.setTags" + ] + }, + "destroy": [ + "compute.instances.delete" + ], + "modify": [], + "plan": [] + } +] diff --git a/src/policy.go b/src/policy.go index ce51e6b8..f6f5c383 100644 --- a/src/policy.go +++ b/src/policy.go @@ -6,6 +6,8 @@ import ( "encoding/json" "errors" "fmt" + "log" + "reflect" "sort" "strconv" "strings" @@ -15,8 +17,8 @@ import ( //go:embed terraform.policy.template var policyTemplate []byte -// NewPolicy constructor -func NewPolicy(Actions []string) Policy { +// NewAWSPolicy constructor +func NewAWSPolicy(Actions []string) Policy { something := Policy{} something.Version = "2012-10-17" @@ -54,29 +56,53 @@ func NewPolicy(Actions []string) Policy { // GetPolicy creates new iam polices from a list of Permissions func GetPolicy(actions Sorted, output string) (string, error) { - var Permissions []string - Permissions = append(Permissions, actions.AWS...) + v := reflect.ValueOf(actions) + typeOfV := v.Type() + values := make([]interface{}, v.NumField()) - if Permissions == nil { - return "", errors.New("no permissions detected") - } + var Policy string + var err error + for i := 0; i < v.NumField(); i++ { + values[i] = v.Field(i).Interface() + switch typeOfV.Field(i).Name { + case "AWS": + //AWSPermissions = append(AWSPermissions, actions.AWS...) - //dedupe - Permissions = unique(Permissions) + if actions.AWS == nil { + continue + } + //dedupe + AWSPermissions := unique(actions.AWS) + Policy, err = AWSPolicy(AWSPermissions, output) - Policy, err2 := AWSPolicy(Permissions, output) + if err != nil { + log.Print(err) + continue + } - if err2 != nil { - return "", err2 + case "GCP": + if actions.GCP == nil { + continue + } + //dedupe + GCPPermissions := unique(actions.GCP) + Policy, err = GCPPolicy(GCPPermissions) + if err != nil { + log.Print(err) + continue + } + } + } + if Policy == "" { + return Policy, errors.New("No permissions found") } - return Policy, nil } // AWSPolicy create an IAM policy func AWSPolicy(Permissions []string, output string) (string, error) { - Policy := NewPolicy(Permissions) + Policy := NewAWSPolicy(Permissions) b, err := json.MarshalIndent(Policy, "", " ") if err != nil { fmt.Println(err) diff --git a/src/policy_test.go b/src/policy_test.go index de373122..e10e451c 100644 --- a/src/policy_test.go +++ b/src/policy_test.go @@ -18,7 +18,7 @@ func TestNewPolicy(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := NewPolicy(tt.args.Actions); !reflect.DeepEqual(got, tt.want) { + if got := NewAWSPolicy(tt.args.Actions); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewPolicy() = %v, want %v", got, tt.want) } }) diff --git a/src/scan.go b/src/scan.go index b60a8f9e..7c1159ee 100644 --- a/src/scan.go +++ b/src/scan.go @@ -123,7 +123,7 @@ func MakePolicy(dirName string, output string, file string, init bool) (string, } PermissionBag.AWS = append(PermissionBag.AWS, newPerms.AWS...) - PermissionBag.GCP = append(PermissionBag.GCP, newPerms.AWS...) + PermissionBag.GCP = append(PermissionBag.GCP, newPerms.GCP...) } Policy, err2 := GetPolicy(PermissionBag, output) diff --git a/src/terraform.gcppolicy.template b/src/terraform.gcppolicy.template new file mode 100644 index 00000000..665b5607 --- /dev/null +++ b/src/terraform.gcppolicy.template @@ -0,0 +1,9 @@ +resource "google_project_iam_custom_role" "{{.Name}}" { + project = "{{.Project}}" + role_id = "{{.RoleID}}" + title = "{{.Name}}" + description = "A user with least privileges" + permissions= [ + "{{ .Permissions }}" + ] +} diff --git a/terraform/gcp/Makefile b/terraform/gcp/Makefile new file mode 100644 index 00000000..9c30774c --- /dev/null +++ b/terraform/gcp/Makefile @@ -0,0 +1,21 @@ +clean: + -rm -fr .terraform + -rm .terraform.lock.hcl + -rm terraform.tfstate + -rm terraform.tfstate.backup +apply: init + terraform apply -auto-approve + +plan: init + terraform plan + +destroy: init + terraform destroy -auto-approve + +init: + terraform init + +role: FORCE + terraform -chdir=./role apply -auto-approve + +FORCE: diff --git a/terraform/gcp/backup/google_compute_instance.tf b/terraform/gcp/backup/google_compute_instance.tf new file mode 100644 index 00000000..f012a1c2 --- /dev/null +++ b/terraform/gcp/backup/google_compute_instance.tf @@ -0,0 +1,40 @@ +resource "google_compute_instance" "default" { + name = "test" + machine_type = "e2-micro" + zone = "europe-west2-a" + + + + boot_disk { + initialize_params { + image = "debian-cloud/debian-11" + } + } + + // Local SSD disk + # scratch_disk { + # interface = "SCSI" + # } + + network_interface { + network = "default" + + access_config { + // Ephemeral public IP + } + } + + metadata = { + foo = "bar" + } + + metadata_startup_script = "echo hi > /test.txt" + + # service_account { + # # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles. + # email = google_service_account.default.email + # scopes = ["cloud-platform"] + # } + + tags = ["foo", "bar"] +} diff --git a/terraform/gcp/gcp_instance.tf b/terraform/gcp/gcp_instance.tf deleted file mode 100644 index 55501062..00000000 --- a/terraform/gcp/gcp_instance.tf +++ /dev/null @@ -1,38 +0,0 @@ -resource "google_compute_instance" "default" { - name = "test" - machine_type = "e2-medium" - zone = "us-central1-a" - - tags = ["foo", "bar"] - - boot_disk { - initialize_params { - image = "debian-cloud/debian-9" - } - } - - // Local SSD disk - scratch_disk { - interface = "SCSI" - } - - network_interface { - network = "default" - - access_config { - // Ephemeral public IP - } - } - - metadata = { - foo = "bar" - } - - metadata_startup_script = "echo hi > /test.txt" - - service_account { - # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles. - email = google_service_account.default.email - scopes = ["cloud-platform"] - } -} diff --git a/terraform/gcp/provider.google.tf b/terraform/gcp/provider.google.tf new file mode 100644 index 00000000..2bb5b42a --- /dev/null +++ b/terraform/gcp/provider.google.tf @@ -0,0 +1,6 @@ +provider "google" { + project = "examplea" + region = "europe-west2" + zone = "europe-west2-a" + credentials = "/Users/jameswoolfenden/examplea-pike.json" +} diff --git a/terraform/gcp/role/google_project_iam_binding.tf b/terraform/gcp/role/google_project_iam_binding.tf new file mode 100644 index 00000000..2d907ac4 --- /dev/null +++ b/terraform/gcp/role/google_project_iam_binding.tf @@ -0,0 +1,8 @@ +resource "google_project_iam_binding" "pike" { + project = "examplea" + role = google_project_iam_custom_role.pike.id + + members = [ + "serviceAccount:${google_service_account.pike.email}", + ] +} diff --git a/terraform/gcp/role/google_project_iam_custom_role.tf b/terraform/gcp/role/google_project_iam_custom_role.tf new file mode 100644 index 00000000..e54cd4ee --- /dev/null +++ b/terraform/gcp/role/google_project_iam_custom_role.tf @@ -0,0 +1,18 @@ +resource "google_project_iam_custom_role" "pike" { + project = "examplea" + role_id = "terraform_pike" + title = "pike terraform user" + description = "A user with least privileges" + permissions = [ + "compute.zones.get", + "compute.instances.create", + "compute.instances.get", + "compute.disks.create", + "compute.disks.create", + "compute.subnetworks.use", + "compute.subnetworks.useExternalIp", + "compute.instances.setMetadata", + "compute.instances.delete", + "compute.instances.setTags" + ] +} diff --git a/terraform/gcp/role/google_service_account.tf b/terraform/gcp/role/google_service_account.tf new file mode 100644 index 00000000..baf56846 --- /dev/null +++ b/terraform/gcp/role/google_service_account.tf @@ -0,0 +1,5 @@ +resource "google_service_account" "pike" { + account_id = "pike-service" + display_name = "pike" + project = "examplea" +} diff --git a/terraform/gcp/role/output.tf b/terraform/gcp/role/output.tf new file mode 100644 index 00000000..92fee6c0 --- /dev/null +++ b/terraform/gcp/role/output.tf @@ -0,0 +1,11 @@ +output "service_account" { + value = google_service_account.pike +} + +output "iam_binding" { + value = google_project_iam_binding.pike +} + +output "custom_role" { + value = google_project_iam_custom_role.pike +}