From 4787a666a64d385603e402cb61eee1c25317b821 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Mon, 20 May 2024 11:17:41 +0200 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20AWS=20integration=20(#83)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⭐️ AWS integration * 🧹 update mondoo schema * 🧹 remove region argument --- docs/resources/integration_aws.md | 92 ++++++ .../resources/mondoo_integration_aws/main.tf | 9 + .../mondoo_integration_aws/resource.tf | 36 +++ go.mod | 2 +- go.sum | 2 + internal/provider/integration_aws_resource.go | 265 ++++++++++++++++++ internal/provider/provider.go | 5 +- 7 files changed, 408 insertions(+), 3 deletions(-) create mode 100644 docs/resources/integration_aws.md create mode 100644 examples/resources/mondoo_integration_aws/main.tf create mode 100644 examples/resources/mondoo_integration_aws/resource.tf create mode 100644 internal/provider/integration_aws_resource.go diff --git a/docs/resources/integration_aws.md b/docs/resources/integration_aws.md new file mode 100644 index 0000000..6ea5661 --- /dev/null +++ b/docs/resources/integration_aws.md @@ -0,0 +1,92 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "mondoo_integration_aws Resource - terraform-provider-mondoo" +subcategory: "" +description: |- + Continuously scan Google AWS organization and accounts for misconfigurations and vulnerabilities. +--- + +# mondoo_integration_aws (Resource) + +Continuously scan Google AWS organization and accounts for misconfigurations and vulnerabilities. + +## Example Usage + +```terraform +variable "mondoo_org" { + description = "Mondoo Organization" + type = string +} + +variable "aws_access_key" { + description = "AWS access key" + type = string +} + +variable "aws_secret_key" { + description = "AWS access key" + type = string +} + +provider "mondoo" {} + +# Create a new space +resource "mondoo_space" "my_space" { + name = "AWS Terraform" + org_id = var.mondoo_org +} + +# Setup the AWS integration +resource "mondoo_integration_aws" "name" { + space_id = mondoo_space.my_space.id + name = "AWS Integration" + + credentials = { + key = { + access_key = var.aws_access_key + secret_key = var.aws_secret_key + } + } +} +``` + + +## Schema + +### Required + +- `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) +- `name` (String) Name of the integration. +- `space_id` (String) Mondoo Space Identifier. + +### Read-Only + +- `mrn` (String) Integration identifier + + +### Nested Schema for `credentials` + +Optional: + +- `key` (Attributes) (see [below for nested schema](#nestedatt--credentials--key)) +- `role` (Attributes) (see [below for nested schema](#nestedatt--credentials--role)) + + +### Nested Schema for `credentials.key` + +Required: + +- `access_key` (String, Sensitive) +- `secret_key` (String, Sensitive) + + + +### Nested Schema for `credentials.role` + +Required: + +- `role_arn` (String, Sensitive) + +Optional: + +- `external_id` (String, Sensitive) diff --git a/examples/resources/mondoo_integration_aws/main.tf b/examples/resources/mondoo_integration_aws/main.tf new file mode 100644 index 0000000..c487c32 --- /dev/null +++ b/examples/resources/mondoo_integration_aws/main.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + mondoo = { + source = "mondoohq/mondoo" + version = ">= 0.4.0" + } + } +} + diff --git a/examples/resources/mondoo_integration_aws/resource.tf b/examples/resources/mondoo_integration_aws/resource.tf new file mode 100644 index 0000000..b0509ba --- /dev/null +++ b/examples/resources/mondoo_integration_aws/resource.tf @@ -0,0 +1,36 @@ +variable "mondoo_org" { + description = "Mondoo Organization" + type = string +} + +variable "aws_access_key" { + description = "AWS access key" + type = string +} + +variable "aws_secret_key" { + description = "AWS access key" + type = string +} + +provider "mondoo" {} + +# Create a new space +resource "mondoo_space" "my_space" { + name = "AWS Terraform" + org_id = var.mondoo_org +} + +# Setup the AWS integration +resource "mondoo_integration_aws" "name" { + space_id = mondoo_space.my_space.id + name = "AWS Integration" + + credentials = { + key = { + access_key = var.aws_access_key + secret_key = var.aws_secret_key + } + } +} + diff --git a/go.mod b/go.mod index d0bbca3..a23ec70 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 github.com/stretchr/testify v1.9.0 - go.mondoo.com/mondoo-go v0.0.0-20240507081602-aa7a34bcb66d + go.mondoo.com/mondoo-go v0.0.0-20240516194133-d6612b90fe7c ) require ( diff --git a/go.sum b/go.sum index e3a4849..bdc1b8f 100644 --- a/go.sum +++ b/go.sum @@ -495,6 +495,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.mondoo.com/mondoo-go v0.0.0-20240507081602-aa7a34bcb66d h1:f/vvw9UYM/iZ3XODFcndiHiu5ikV35vLw1m+lOYxYtY= go.mondoo.com/mondoo-go v0.0.0-20240507081602-aa7a34bcb66d/go.mod h1:XY+tOP6vBFJKw5F3WLYEHNQxc+6YmfQ+hEbw3yRy3HI= +go.mondoo.com/mondoo-go v0.0.0-20240516194133-d6612b90fe7c h1:y910hpEdf1rYW/ONzc0NhuTwVDQNJNC9+x1C+xGywAI= +go.mondoo.com/mondoo-go v0.0.0-20240516194133-d6612b90fe7c/go.mod h1:XY+tOP6vBFJKw5F3WLYEHNQxc+6YmfQ+hEbw3yRy3HI= go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/internal/provider/integration_aws_resource.go b/internal/provider/integration_aws_resource.go new file mode 100644 index 0000000..2cadd9d --- /dev/null +++ b/internal/provider/integration_aws_resource.go @@ -0,0 +1,265 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "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/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + mondoov1 "go.mondoo.com/mondoo-go" +) + +var _ resource.Resource = (*integrationAwsResource)(nil) + +func NewIntegrationAwsResource() resource.Resource { + return &integrationAwsResource{} +} + +type integrationAwsResource struct { + client *ExtendedGqlClient +} + +type integrationAwsResourceModel struct { + // scope + SpaceId types.String `tfsdk:"space_id"` + + // integration details + Mrn types.String `tfsdk:"mrn"` + Name types.String `tfsdk:"name"` + + // AWS credentials + Credential integrationAwsCredentialModel `tfsdk:"credentials"` +} + +type integrationAwsCredentialModel struct { + Role *roleCredentialModel `tfsdk:"role"` + Key *accessKeyCredentialModel `tfsdk:"key"` +} + +type roleCredentialModel struct { + RoleArn types.String `tfsdk:"role_arn"` + ExternalId types.String `tfsdk:"external_id"` +} + +type accessKeyCredentialModel struct { + AccessKey types.String `tfsdk:"access_key"` + SecretKey types.String `tfsdk:"secret_key"` +} + +func (m integrationAwsResourceModel) GetConfigurationOptions() *mondoov1.HostedAwsConfigurationOptionsInput { + opts := &mondoov1.HostedAwsConfigurationOptionsInput{} + + if m.Credential.Key != nil { + opts.KeyCredential = &mondoov1.AWSSecretKeyCredential{ + AccessKeyID: mondoov1.String(m.Credential.Key.AccessKey.ValueString()), + SecretAccessKey: mondoov1.String(m.Credential.Key.SecretKey.ValueString()), + } + } + + if m.Credential.Role != nil { + var externalID *mondoov1.String + externalIDValue := m.Credential.Role.ExternalId.ValueString() + if externalIDValue == "" { + externalID = mondoov1.NewStringPtr(mondoov1.String(externalIDValue)) + } + + opts.RoleCredential = &mondoov1.AWSRoleCredential{ + Role: mondoov1.String(m.Credential.Role.RoleArn.ValueString()), + ExternalID: externalID, + } + } + + return opts +} + +func (r *integrationAwsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_integration_aws" +} + +func (r *integrationAwsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: `Continuously scan Google AWS organization and accounts for misconfigurations and vulnerabilities.`, + Attributes: map[string]schema.Attribute{ + "space_id": schema.StringAttribute{ + MarkdownDescription: "Mondoo Space Identifier.", + Required: true, + }, + "mrn": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Integration identifier", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the integration.", + Required: true, + }, + "credentials": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "role": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "role_arn": schema.StringAttribute{ + Required: true, + Sensitive: true, + }, + "external_id": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + }, + Validators: []validator.Object{ + // Validate this attribute must not be configured with other_attr. + objectvalidator.ConflictsWith(path.Expressions{ + path.MatchRoot("credentials").AtName("key"), + }...), + }, + }, + "key": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "access_key": schema.StringAttribute{ + Required: true, + Sensitive: true, + }, + "secret_key": schema.StringAttribute{ + Required: true, + Sensitive: true, + }, + }, + }, + }, + }, + }, + } +} + +func (r *integrationAwsResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*mondoov1.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = &ExtendedGqlClient{client} +} + +func (r *integrationAwsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data integrationAwsResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Do GraphQL request to API to create the resource. + spaceMrn := "" + if data.SpaceId.ValueString() != "" { + spaceMrn = spacePrefix + data.SpaceId.ValueString() + } + + integration, err := r.client.CreateIntegration(ctx, + spaceMrn, + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeAwsHosted, + mondoov1.ClientIntegrationConfigurationInput{ + AwsHostedConfigurationOptions: data.GetConfigurationOptions(), + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create AWS integration, got error: %s", err)) + return + } + + // Save space mrn into the Terraform state. + data.Mrn = types.StringValue(string(integration.Mrn)) + data.Name = types.StringValue(string(integration.Name)) + data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *integrationAwsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data integrationAwsResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read API call logic + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *integrationAwsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data integrationAwsResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Do GraphQL request to API to update the resource. + opts := mondoov1.ClientIntegrationConfigurationInput{ + AwsHostedConfigurationOptions: data.GetConfigurationOptions(), + } + + _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeAwsHosted, opts) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update AWS integration, got error: %s", err)) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *integrationAwsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data integrationAwsResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Do GraphQL request to API to update the resource. + _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete AWS integration, got error: %s", err)) + return + } +} + +func (r *integrationAwsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("mrn"), req, resp) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7414651..98884d8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -161,11 +161,12 @@ func (p *MondooProvider) Resources(ctx context.Context) []func() resource.Resour NewPolicyAssigmentResource, NewCustomQueryPackResource, NewQueryPackAssigmentResource, + NewScimGroupMappingResource, NewIntegrationAzureResource, + NewIntegrationAwsResource, + NewIntegrationDomainResource, NewIntegrationGcpResource, NewIntegrationOciTenantResource, - NewScimGroupMappingResource, - NewIntegrationDomainResource, NewIntegrationSlackResource, NewIntegrationMs365Resource, NewIntegrationGithubResource,