Skip to content
This repository has been archived by the owner on Jan 23, 2025. It is now read-only.

Add new rule: Google IAM Workload Identity Pool Provider with no conditions #1338

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions avd_docs/google/iam/AVD-GCP-0068/Terraform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

Set conditions on this provider, for example by restricting it to only be allowed from repositories in your GitHub organization.

```hcl
resource "google_iam_workload_identity_pool_provider" "github" {
project = "example-project"
workload_identity_pool_id = "example-pool"
workload_identity_pool_provider_id = "example-provider"

attribute_condition = "assertion.repository_owner=='your-github-organization'"

attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.aud" = "assertion.aud"
"attribute.repository" = "assertion.repository"
}
}
```

#### Remediation Links

- https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider#attribute_condition

11 changes: 11 additions & 0 deletions avd_docs/google/iam/AVD-GCP-0068/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

In GitHub Actions, one can authenticate to Google Cloud by setting values for workload_identity_provider and service_account and requesting a short-lived OIDC token which is then used to execute commands as that Service Account. If you don't specify a condition in the workload identity provider pool configuration, then any GitHub Action can assume this role and act as that Service Account.

### Impact
Privilege escalation, impersonation of service accounts

<!-- DO NOT CHANGE -->
{{ remediationActions }}

### Links
- https://www.revblock.dev/exploiting-misconfigured-google-cloud-service-accounts-from-github-actions/
13 changes: 8 additions & 5 deletions internal/adapters/terraform/google/iam/adapt.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ func Adapt(modules terraform.Modules) iam.IAM {
}

type adapter struct {
modules terraform.Modules
orgs map[string]iam.Organization
folders []parentedFolder
projects []parentedProject
modules terraform.Modules
orgs map[string]iam.Organization
folders []parentedFolder
projects []parentedProject
workloadIdentityPoolProviders []iam.WorkloadIdentityPoolProvider
}

func (a *adapter) Adapt() iam.IAM {
Expand All @@ -27,6 +28,7 @@ func (a *adapter) Adapt() iam.IAM {
a.adaptFolderIAM()
a.adaptProjects()
a.adaptProjectIAM()
a.adaptWorkloadIdentityPoolProviders()
return a.merge()
}

Expand Down Expand Up @@ -96,7 +98,8 @@ FOLDER_ORG:
}

output := iam.IAM{
Organizations: nil,
Organizations: nil,
WorkloadIdentityPoolProviders: a.workloadIdentityPoolProviders,
}
for _, org := range a.orgs {
output.Organizations = append(output.Organizations, org)
Expand Down
55 changes: 40 additions & 15 deletions internal/adapters/terraform/google/iam/adapt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,38 @@ func Test_Adapt(t *testing.T) {
terraform: `
data "google_organization" "org" {
domain = "example.com"
}
}

resource "google_project" "my_project" {
resource "google_project" "my_project" {
name = "My Project"
project_id = "your-project-id"
org_id = data.google_organization.org.id
auto_create_network = true
}
}

resource "google_folder" "department1" {
resource "google_folder" "department1" {
display_name = "Department 1"
parent = data.google_organization.org.id
}
}

resource "google_folder_iam_member" "admin" {
resource "google_folder_iam_member" "admin" {
folder = google_folder.department1.name
role = "roles/editor"
member = "user:[email protected]"
}
}

resource "google_folder_iam_binding" "folder-123" {
folder = google_folder.department1.name
role = "roles/nothing"
members = [
"user:[email protected]",
]
]
}

resource "google_organization_iam_member" "org-123" {
org_id = data.google_organization.org.id
role = "roles/whatever"
member = "user:[email protected]"
org_id = data.google_organization.org.id
role = "roles/whatever"
member = "user:[email protected]"
}

resource "google_organization_iam_binding" "binding" {
Expand All @@ -64,7 +64,13 @@ func Test_Adapt(t *testing.T) {
members = [
"user:[email protected]",
]
}
}

resource "google_iam_workload_identity_pool_provider" "example" {
workload_identity_pool_id = "example-pool"
workload_identity_pool_provider_id = "example-provider"
attribute_condition = "assertion.repository_owner=='your-github-organization'"
}
`,
expected: iam.IAM{
Organizations: []iam.Organization{
Expand Down Expand Up @@ -120,6 +126,15 @@ func Test_Adapt(t *testing.T) {
},
},
},
WorkloadIdentityPoolProviders: []iam.WorkloadIdentityPoolProvider{
{
Metadata: defsecTypes.NewTestMetadata(),

WorkloadIdentityPoolId: defsecTypes.String("example-pool", defsecTypes.NewTestMetadata()),
WorkloadIdentityPoolProviderId: defsecTypes.String("example-provider", defsecTypes.NewTestMetadata()),
AttributeCondition: defsecTypes.String("assertion.repository_owner=='your-github-organization'", defsecTypes.NewTestMetadata()),
},
},
},
},
}
Expand Down Expand Up @@ -166,9 +181,9 @@ func TestLines(t *testing.T) {
}

resource "google_organization_iam_member" "org-123" {
org_id = data.google_organization.org.id
role = "roles/whatever"
member = "user:[email protected]"
org_id = data.google_organization.org.id
role = "roles/whatever"
member = "user:[email protected]"
}

resource "google_organization_iam_binding" "binding" {
Expand All @@ -178,6 +193,12 @@ func TestLines(t *testing.T) {
members = [
"user:[email protected]",
]
}

resource "google_iam_workload_identity_pool_provider" "example" {
workload_identity_pool_id = "example-pool"
workload_identity_pool_provider_id = "example-provider"
attribute_condition = "assertion.repository_owner=='your-github-organization'"
}`

modules := tftestutil.CreateModulesFromSource(t, src, ".tf")
Expand All @@ -188,11 +209,13 @@ func TestLines(t *testing.T) {
require.Len(t, adapted.Organizations[0].Folders, 1)
require.Len(t, adapted.Organizations[0].Bindings, 1)
require.Len(t, adapted.Organizations[0].Members, 1)
require.Len(t, adapted.WorkloadIdentityPoolProviders, 1)

project := adapted.Organizations[0].Projects[0]
folder := adapted.Organizations[0].Folders[0]
binding := adapted.Organizations[0].Bindings[0]
member := adapted.Organizations[0].Members[0]
pool := adapted.WorkloadIdentityPoolProviders[0]

assert.Equal(t, 6, project.Metadata.Range().GetStartLine())
assert.Equal(t, 11, project.Metadata.Range().GetEndLine())
Expand Down Expand Up @@ -238,4 +261,6 @@ func TestLines(t *testing.T) {

assert.Equal(t, 42, binding.Members[0].GetMetadata().Range().GetStartLine())
assert.Equal(t, 44, binding.Members[0].GetMetadata().Range().GetEndLine())

assert.Equal(t, 51, pool.Metadata.Range().GetEndLine())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package iam

import (
"github.com/aquasecurity/defsec/pkg/providers/google/iam"
)

// See https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider

func (a *adapter) adaptWorkloadIdentityPoolProviders() {
for _, resource := range a.modules.GetResourcesByType("google_iam_workload_identity_pool_provider") {
a.workloadIdentityPoolProviders = append(a.workloadIdentityPoolProviders, iam.WorkloadIdentityPoolProvider{
Metadata: resource.GetMetadata(),
WorkloadIdentityPoolId: resource.GetAttribute("workload_identity_pool_id").AsStringValueOrDefault("", resource),
WorkloadIdentityPoolProviderId: resource.GetAttribute("workload_identity_pool_provider_id").AsStringValueOrDefault("", resource),
AttributeCondition: resource.GetAttribute("attribute_condition").AsStringValueOrDefault("", resource),
})
}
}
10 changes: 9 additions & 1 deletion pkg/providers/google/iam/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import (
)

type IAM struct {
Organizations []Organization
Organizations []Organization
WorkloadIdentityPoolProviders []WorkloadIdentityPoolProvider
}

type Organization struct {
Expand Down Expand Up @@ -45,6 +46,13 @@ type Member struct {
DefaultServiceAccount defsecTypes.BoolValue
}

type WorkloadIdentityPoolProvider struct {
Metadata defsecTypes.Metadata
WorkloadIdentityPoolId defsecTypes.StringValue
WorkloadIdentityPoolProviderId defsecTypes.StringValue
AttributeCondition defsecTypes.StringValue
}

func (p *IAM) AllProjects() []Project {
var projects []Project
for _, org := range p.Organizations {
Expand Down
24 changes: 24 additions & 0 deletions pkg/rego/schemas/cloud.json
Original file line number Diff line number Diff line change
Expand Up @@ -5375,6 +5375,13 @@
"type": "object",
"$ref": "#/definitions/jackfan.us.kg.aquasecurity.defsec.pkg.providers.google.iam.Organization"
}
},
"workloadidentitypoolproviders": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/jackfan.us.kg.aquasecurity.defsec.pkg.providers.google.iam.WorkloadIdentityPoolProvider"
}
}
}
},
Expand Down Expand Up @@ -5451,6 +5458,23 @@
}
}
},
"jackfan.us.kg.aquasecurity.defsec.pkg.providers.google.iam.WorkloadIdentityPoolProvider": {
"type": "object",
"properties": {
"attributecondition": {
"type": "object",
"$ref": "#/definitions/jackfan.us.kg.aquasecurity.defsec.pkg.types.StringValue"
},
"workloadidentitypoolid": {
"type": "object",
"$ref": "#/definitions/jackfan.us.kg.aquasecurity.defsec.pkg.types.StringValue"
},
"workloadidentitypoolproviderid": {
"type": "object",
"$ref": "#/definitions/jackfan.us.kg.aquasecurity.defsec.pkg.types.StringValue"
}
}
},
"jackfan.us.kg.aquasecurity.defsec.pkg.providers.google.kms.KMS": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package iam

import (
"github.com/aquasecurity/defsec/internal/rules"
"github.com/aquasecurity/defsec/pkg/providers"
"github.com/aquasecurity/defsec/pkg/scan"
"github.com/aquasecurity/defsec/pkg/severity"
"github.com/aquasecurity/defsec/pkg/state"
)

var CheckNoConditionOnWorkloadIdentityPoolProvider = rules.Register(
scan.Rule{
AVDID: "AVD-GCP-0068",
Provider: providers.GoogleProvider,
Service: "iam",
ShortCode: "no-conditions-workload-identity-pool-provider",
Summary: "A configuration for an external workload identity pool provider should have conditions set",
Impact: "Allows an external attacker to authenticate as the attached service account and act with its permissions",
Resolution: "Set conditions on this provider, for example by restricting it to only be allowed from repositories in your GitHub organization",
Explanation: `In GitHub Actions, one can authenticate to Google Cloud by setting values for workload_identity_provider and service_account and requesting a short-lived OIDC token which is then used to execute commands as that Service Account. If you don't specify a condition in the workload identity provider pool configuration, then any GitHub Action can assume this role and act as that Service Account.`,
Links: []string{
"https://www.revblock.dev/exploiting-misconfigured-google-cloud-service-accounts-from-github-actions/",
},
Terraform: &scan.EngineMetadata{
GoodExamples: terraformNoConditionOnWorkloadIdentityPoolProviderGoodExamples,
BadExamples: terraformNoConditionOnWorkloadIdentityPoolProviderBadExamples,
Links: terraformNoConditionOnWorkloadIdentityPoolProviderLinks,
RemediationMarkdown: terraformNoConditionOnWorkloadIdentityPoolProviderMarkdown,
},
Severity: severity.High,
},
func(s *state.State) (results scan.Results) {
for _, provider := range s.Google.IAM.WorkloadIdentityPoolProviders {
if provider.AttributeCondition.IsEmpty() {
results.Add(
"This workload identity pool provider configuration has no conditions set.",
provider.AttributeCondition,
)
} else {
results.AddPassed(provider)
}
}
return
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package iam

var terraformNoConditionOnWorkloadIdentityPoolProviderGoodExamples = []string{
`
resource "google_iam_workload_identity_pool" "github" {
provider = google
project = data.google_project.project.project_id
workload_identity_pool_id = "github"
}

resource "google_iam_workload_identity_pool_provider" "github" {
provider = google
project = data.google_project.project.project_id
workload_identity_pool_id = google_iam_workload_identity_pool.github-actions[0].workload_identity_pool_id
workload_identity_pool_provider_id = "github"

attribute_condition = "assertion.repository_owner=='your-github-organization'"

attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.aud" = "assertion.aud"
"attribute.repository" = "assertion.repository"
}

oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
`,
}

var terraformNoConditionOnWorkloadIdentityPoolProviderBadExamples = []string{
`
resource "google_iam_workload_identity_pool" "github" {
provider = google
project = data.google_project.project.project_id
workload_identity_pool_id = "github"
}

resource "google_iam_workload_identity_pool_provider" "github" {
provider = google
project = data.google_project.project.project_id
workload_identity_pool_id = google_iam_workload_identity_pool.github-actions[0].workload_identity_pool_id
workload_identity_pool_provider_id = "github"

attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.aud" = "assertion.aud"
"attribute.repository" = "assertion.repository"
}

oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
`,
}

var terraformNoConditionOnWorkloadIdentityPoolProviderLinks = []string{
`https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider#attribute_condition`,
}

var terraformNoConditionOnWorkloadIdentityPoolProviderMarkdown = ``
Loading