Skip to content

Commit

Permalink
gcp: Add p0_gcp_organization_iam_assessment (#35)
Browse files Browse the repository at this point in the history
This supports the new organization-level IAM assessment component in P0
GCP.
  • Loading branch information
nbrahms authored Jul 1, 2024
1 parent c72cf25 commit 156e8bc
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 2 deletions.
18 changes: 18 additions & 0 deletions docs/resources/gcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ resource "p0_gcp" "example" {
### Read-Only

- `access_logs` (Attributes) Read-only attributes used to configure infrastructure and IAM grants for access-logs integrations (see [below for nested schema](#nestedatt--access_logs))
- `iam_assessment` (Attributes) Read-only attributes used to configure IAM grants for IAM-assessment integrations (see [below for nested schema](#nestedatt--iam_assessment))
- `org_wide_policy` (Attributes) Read-only attributes used to configure IAM grants for org-wide policy-read installation (see [below for nested schema](#nestedatt--org_wide_policy))
- `service_account_email` (String) The identity that P0 uses to communicate with your Google Cloud organization

Expand Down Expand Up @@ -59,6 +60,23 @@ Read-Only:



<a id="nestedatt--iam_assessment"></a>
### Nested Schema for `iam_assessment`

Read-Only:

- `permissions` (Attributes) Permissions that must be granted to P0's service account (see [below for nested schema](#nestedatt--iam_assessment--permissions))

<a id="nestedatt--iam_assessment--permissions"></a>
### Nested Schema for `iam_assessment.permissions`

Read-Only:

- `organization` (List of String) Permissions, in addition to 'project' permissions, required for organization-level IAM-assessment installs
- `project` (List of String) Permissions required for project-level IAM-assessment installs



<a id="nestedatt--org_wide_policy"></a>
### Nested Schema for `org_wide_policy`

Expand Down
65 changes: 65 additions & 0 deletions docs/resources/gcp_organization_iam_assessment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "p0_gcp_organization_iam_assessment Resource - p0"
subcategory: ""
description: |-
An installation of P0, on an entire Google Cloud organization, for IAM assessment.
To use this resource, you must also:
create a custom role allowing IAM-assessment operations, andgrant this custom role to P0's service account.
Use the read-only attributes defined on p0_gcp to create the requisite Google Cloud infrastructure.
P0 recommends defining this infrastructure according to the example usage pattern.
---

# p0_gcp_organization_iam_assessment (Resource)

An installation of P0, on an entire Google Cloud organization, for IAM assessment.

To use this resource, you must also:
- create a custom role allowing IAM-assessment operations, and
- grant this custom role to P0's service account.

Use the read-only attributes defined on `p0_gcp` to create the requisite Google Cloud infrastructure.

P0 recommends defining this infrastructure according to the example usage pattern.

## Example Usage

```terraform
resource "p0_gcp" "example" {
organization_id = "my_gcp_organization_id"
}
# This role grants P0 access to analyze your organization's IAM configuration and asset inventory
resource "google_organization_iam_custom_role" "example" {
org_id = p0_gcp.organization_id
role_id = "p0IamAssessor"
title = "P0 IAM assessor"
description = "Integration role for org-wide P0 IAM assessment integration"
permissions = concat(
p0_gcp.example.iam_assessment.permissions.project,
p0_gcp.example.iam_assessment.permissions.organization
)
}
resource "google_organization_iam_member" "example" {
org_id = p0_gcp.example.organization_id
role = google_organization_iam_custom_role.example.id
member = "serviceAccount:${p0_gcp.gcp.service_account_email}"
}
# The `p0_gcp_organization_iam_assessment` resource will fail to validate unless it is installed
# _after_ the P0 service account is granted the above role
resource "p0_gcp_organization_iam_assessment" "example" {
depends_on = [google_organization_iam_member.example]
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Read-Only

- `state` (String) This item's install progress in the P0 application:
- 'stage': The item has been staged for installation
- 'configure': The item is available to be added to P0, and may be configured
- 'installed': The item is fully installed
27 changes: 27 additions & 0 deletions examples/resources/p0_gcp_organization_iam_assessment/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
resource "p0_gcp" "example" {
organization_id = "my_gcp_organization_id"
}

# This role grants P0 access to analyze your organization's IAM configuration and asset inventory
resource "google_organization_iam_custom_role" "example" {
org_id = p0_gcp.organization_id
role_id = "p0IamAssessor"
title = "P0 IAM assessor"
description = "Integration role for org-wide P0 IAM assessment integration"
permissions = concat(
p0_gcp.example.iam_assessment.permissions.project,
p0_gcp.example.iam_assessment.permissions.organization
)
}

resource "google_organization_iam_member" "example" {
org_id = p0_gcp.example.organization_id
role = google_organization_iam_custom_role.example.id
member = "serviceAccount:${p0_gcp.gcp.service_account_email}"
}

# The `p0_gcp_organization_iam_assessment` resource will fail to validate unless it is installed
# _after_ the P0 service account is granted the above role
resource "p0_gcp_organization_iam_assessment" "example" {
depends_on = [google_organization_iam_member.example]
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func (p *P0Provider) Resources(ctx context.Context) []func() resource.Resource {
installgcp.NewGcpIamWrite,
installgcp.NewGcpIamWriteStaged,
installgcp.NewGcpOrgAccessLogs,
installgcp.NewGcpOrgIamAssessment,
installgcp.NewGcpSharingRestriction,
installssh.NewSshAwsIamWrite,
installssh.NewSshGcpIamWrite,
Expand Down
1 change: 1 addition & 0 deletions internal/provider/resources/install/gcp/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
AccessLogs = "access-logs"
GcpKey = "gcloud"
OrgAccessLogs = "org-access-logs"
OrgIamAssessment = "org-iam-assessment"
SharingRestriction = "sharing-restriction"
)

Expand Down
56 changes: 54 additions & 2 deletions internal/provider/resources/install/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type gcpModel struct {
OrganizationId types.String `tfsdk:"organization_id"`
ServiceAccountEmail types.String `tfsdk:"service_account_email"`
AccessLogs types.Object `tfsdk:"access_logs"`
IamAssessment types.Object `tfsdk:"iam_assessment"`
OrgWidePolicy types.Object `tfsdk:"org_wide_policy"`
}

Expand All @@ -53,6 +54,11 @@ type gcpAccessLogsMetadata struct {
PubSub gcpAccessLogsPubSubMetadata `json:"pubSub" tfsdk:"pub_sub"`
}

type gcpIamAssessmentMetadata struct {
ProjectPermissions []string `json:"requiredPermissions" tfsdk:"project"`
OrganizationPermissions []string `json:"orgLevelPermissions" tfsdk:"organization"`
}

type gcpApi struct {
Config struct {
Root struct {
Expand All @@ -63,8 +69,9 @@ type gcpApi struct {
} `json:"root"`
} `json:"config"`
Metadata struct {
AccessLogs gcpAccessLogsMetadata `json:"access-logs"`
OrgWidePolicy gcpPermissionsMetadata `json:"org-wide-policy"`
AccessLogs gcpAccessLogsMetadata `json:"access-logs"`
IamAssessment gcpIamAssessmentMetadata `json:"iam-assessment"`
OrgWidePolicy gcpPermissionsMetadata `json:"org-wide-policy"`
} `json:"metadata"`
}

Expand Down Expand Up @@ -125,6 +132,28 @@ func (r *Gcp) Schema(ctx context.Context, req resource.SchemaRequest, resp *reso
},
},
},
"iam_assessment": schema.SingleNestedAttribute{
Computed: true,
MarkdownDescription: `Read-only attributes used to configure IAM grants for IAM-assessment integrations`,
Attributes: map[string]schema.Attribute{
"permissions": schema.SingleNestedAttribute{
Computed: true,
MarkdownDescription: `Permissions that must be granted to P0's service account`,
Attributes: map[string]schema.Attribute{
"project": schema.ListAttribute{
Computed: true,
ElementType: types.StringType,
MarkdownDescription: `Permissions required for project-level IAM-assessment installs`,
},
"organization": schema.ListAttribute{
Computed: true,
ElementType: types.StringType,
MarkdownDescription: `Permissions, in addition to 'project' permissions, required for organization-level IAM-assessment installs`,
},
},
},
},
},
"org_wide_policy": schema.SingleNestedAttribute{
Computed: true,
MarkdownDescription: `Read-only attributes used to configure IAM grants for org-wide policy-read installation`,
Expand Down Expand Up @@ -177,6 +206,29 @@ func (r *Gcp) fromJson(ctx context.Context, diags *diag.Diagnostics, json any) a
}
data.AccessLogs = accessLogs

iamAssessmentPermissionsType := map[string]attr.Type{
"project": types.ListType{ElemType: types.StringType},
"organization": types.ListType{ElemType: types.StringType},
}
iamAssessmentPermissions, iapDiags := types.ObjectValueFrom(
ctx, iamAssessmentPermissionsType, metadata.IamAssessment,
)
if iapDiags.HasError() {
diags.Append(iapDiags...)
return nil
}
iamAssessment, iaDiags := types.ObjectValue(
map[string]attr.Type{"permissions": types.ObjectType{
AttrTypes: iamAssessmentPermissionsType,
}},
map[string]attr.Value{"permissions": iamAssessmentPermissions},
)
if iaDiags.HasError() {
diags.Append(iaDiags...)
return nil
}
data.IamAssessment = iamAssessment

orgWidePolicy, owDiags := types.ObjectValueFrom(ctx, map[string]attr.Type{
"custom_role": types.ObjectType{
AttrTypes: map[string]attr.Type{
Expand Down
99 changes: 99 additions & 0 deletions internal/provider/resources/install/gcp/org_iam_assessment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package installgcp

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/p0-security/terraform-provider-p0/internal"
installresources "github.com/p0-security/terraform-provider-p0/internal/provider/resources/install"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &GcpOrgIamAssessment{}
var _ resource.ResourceWithImportState = &GcpOrgIamAssessment{}
var _ resource.ResourceWithConfigure = &GcpOrgIamAssessment{}

func NewGcpOrgIamAssessment() resource.Resource {
return &GcpOrgIamAssessment{}
}

type GcpOrgIamAssessment struct {
installer *installresources.Install
}

type gcpOrgIamAssessmentModel struct {
State types.String `tfsdk:"state"`
}

func (r *GcpOrgIamAssessment) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_gcp_organization_iam_assessment"
}

func (r *GcpOrgIamAssessment) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: `An installation of P0, on an entire Google Cloud organization, for IAM assessment.
To use this resource, you must also:
- create a custom role allowing IAM-assessment operations, and
- grant this custom role to P0's service account.
Use the read-only attributes defined on ` + "`p0_gcp`" + ` to create the requisite Google Cloud infrastructure.
P0 recommends defining this infrastructure according to the example usage pattern.`,
Attributes: map[string]schema.Attribute{
"state": stateAttribute,
},
}
}

func (r *GcpOrgIamAssessment) fromJson(ctx context.Context, diags *diag.Diagnostics, id string, json any) any {
data := gcpOrgIamAssessmentModel{}
jsonv, ok := json.(*gcpItemJson)
if !ok {
return nil
}

data.State = types.StringValue(jsonv.State)

return &data
}

func (r *GcpOrgIamAssessment) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
providerData := internal.Configure(&req, resp)
r.installer = &installresources.Install{
Integration: GcpKey,
Component: OrgIamAssessment,
ProviderData: providerData,
GetId: singletonGetId,
GetItemJson: itemGetItemJson,
FromJson: r.fromJson,
ToJson: itemToJson,
}
}

func (s *GcpOrgIamAssessment) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var model gcpOrgIamAssessmentModel
var json gcpItemApi
s.installer.Stage(ctx, &resp.Diagnostics, &req.Plan, &resp.State, &json, &model)
s.installer.UpsertFromStage(ctx, &resp.Diagnostics, &req.Plan, &resp.State, &json, &model)
}

func (s *GcpOrgIamAssessment) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
s.installer.Read(ctx, &resp.Diagnostics, &resp.State, &gcpItemApi{}, &gcpOrgIamAssessmentModel{})
}

func (s *GcpOrgIamAssessment) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
s.installer.Delete(ctx, &resp.Diagnostics, &req.State, &gcpOrgIamAssessmentModel{})
}

func (s *GcpOrgIamAssessment) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
s.installer.UpsertFromStage(ctx, &resp.Diagnostics, &req.Plan, &resp.State, &gcpItemApi{}, &gcpOrgIamAssessmentModel{})
}

func (s *GcpOrgIamAssessment) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Empty(), req, resp)
}

0 comments on commit 156e8bc

Please sign in to comment.