diff --git a/docs/resources/export_bigquery.md b/docs/resources/export_bigquery.md
index b592e8b..6ebf7b2 100644
--- a/docs/resources/export_bigquery.md
+++ b/docs/resources/export_bigquery.md
@@ -36,14 +36,33 @@ Export data to Google BigQuery.
- `dataset_id` (String) Target BigQuery dataset (project-id.dataset_id).
- `name` (String) A descriptive name for the integration.
-- `service_account_key` (String, Sensitive) Google service account JSON key content.
### Optional
+- `credentials` (Attributes) Credentials for the BigQuery export. Provide `wif` for workload identity federation instead of the top-level `service_account_key`. (see [below for nested schema](#nestedatt--credentials))
- `scope_mrn` (String) The MRN of the scope (space, organization, or platform) for the export integration.
+- `service_account_key` (String, Sensitive) Google service account JSON key content. Mutually exclusive with `credentials.wif`.
- `space_id` (String, Deprecated) Mondoo space identifier. If there is no space ID, the provider space is used.
### Read-Only
- `mrn` (String) Mondoo resource name (MRN) of the integration.
- `wif_subject` (String) Computed OIDC subject used when Mondoo requests a WIF token for this integration. Configure your cloud provider's trust policy to accept this subject.
+
+
+### Nested Schema for `credentials`
+
+Optional:
+
+- `wif` (Attributes) Workload identity federation configuration. Mutually exclusive with `service_account_key`. (see [below for nested schema](#nestedatt--credentials--wif))
+
+
+### Nested Schema for `credentials.wif`
+
+Required:
+
+- `audience` (String) WIF audience URL for GCP workload identity federation.
+
+Optional:
+
+- `service_account_email` (String) Optional GCP service account email to impersonate via workload identity federation.
diff --git a/docs/resources/export_gcs_bucket.md b/docs/resources/export_gcs_bucket.md
index 7376f1c..32abf3f 100644
--- a/docs/resources/export_gcs_bucket.md
+++ b/docs/resources/export_gcs_bucket.md
@@ -41,7 +41,7 @@ Export data to a Google Cloud Storage bucket.
### Required
- `bucket_name` (String) Name of the Google Cloud Storage bucket to export data to.
-- `credentials` (Attributes) Credentials for the Google Cloud Storage bucket. (see [below for nested schema](#nestedatt--credentials))
+- `credentials` (Attributes) Credentials for the Google Cloud Storage bucket. Provide either a static service account `private_key` or a `wif` block for workload identity federation. (see [below for nested schema](#nestedatt--credentials))
- `name` (String) Name of the export integration.
### Optional
@@ -58,6 +58,18 @@ Export data to a Google Cloud Storage bucket.
### Nested Schema for `credentials`
+Optional:
+
+- `private_key` (String, Sensitive) Private key for the service account in JSON format. Mutually exclusive with `wif`.
+- `wif` (Attributes) Workload identity federation configuration. Mutually exclusive with `private_key`. (see [below for nested schema](#nestedatt--credentials--wif))
+
+
+### Nested Schema for `credentials.wif`
+
Required:
-- `private_key` (String, Sensitive) Private key for the service account in JSON format.
+- `audience` (String) WIF audience URL for GCP workload identity federation.
+
+Optional:
+
+- `service_account_email` (String) Optional GCP service account email to impersonate via workload identity federation.
diff --git a/docs/resources/integration_aws.md b/docs/resources/integration_aws.md
index 7a63ef9..f888427 100644
--- a/docs/resources/integration_aws.md
+++ b/docs/resources/integration_aws.md
@@ -47,7 +47,7 @@ resource "mondoo_integration_aws" "name" {
### Required
-- `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials))
+- `credentials` (Attributes) Credentials for the AWS integration. Exactly one of `role`, `key`, or `wif` must be configured. (see [below for nested schema](#nestedatt--credentials))
- `name` (String) Name of the integration.
### Optional
@@ -64,8 +64,9 @@ resource "mondoo_integration_aws" "name" {
Optional:
-- `key` (Attributes) (see [below for nested schema](#nestedatt--credentials--key))
-- `role` (Attributes) (see [below for nested schema](#nestedatt--credentials--role))
+- `key` (Attributes) Static IAM access key credentials. Mutually exclusive with `role` and `wif`. (see [below for nested schema](#nestedatt--credentials--key))
+- `role` (Attributes) IAM role credentials. Mutually exclusive with `key` and `wif`. (see [below for nested schema](#nestedatt--credentials--role))
+- `wif` (Attributes) Workload identity federation credentials. Uses Mondoo as an OIDC identity provider to assume an IAM role via web identity. Mutually exclusive with `role` and `key`. (see [below for nested schema](#nestedatt--credentials--wif))
### Nested Schema for `credentials.key`
@@ -87,6 +88,15 @@ Optional:
- `external_id` (String, Sensitive)
+
+
+### Nested Schema for `credentials.wif`
+
+Required:
+
+- `audience` (String) Audience value configured in the AWS IAM OIDC identity provider.
+- `role_arn` (String) ARN of the IAM role to assume via web identity federation.
+
## Import
Import is supported using the following syntax:
diff --git a/docs/resources/integration_gcp.md b/docs/resources/integration_gcp.md
index 49c550f..3f0267a 100644
--- a/docs/resources/integration_gcp.md
+++ b/docs/resources/integration_gcp.md
@@ -73,7 +73,7 @@ resource "mondoo_integration_gcp" "name" {
### Required
-- `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials))
+- `credentials` (Attributes) Credentials for the GCP integration. Provide either a static service account `private_key` or a `wif` block for workload identity federation. (see [below for nested schema](#nestedatt--credentials))
- `name` (String) Name of the integration.
### Optional
@@ -89,9 +89,21 @@ resource "mondoo_integration_gcp" "name" {
### Nested Schema for `credentials`
+Optional:
+
+- `private_key` (String, Sensitive) GCP service account JSON key. Mutually exclusive with `wif`.
+- `wif` (Attributes) Workload identity federation configuration. Mutually exclusive with `private_key`. (see [below for nested schema](#nestedatt--credentials--wif))
+
+
+### Nested Schema for `credentials.wif`
+
Required:
-- `private_key` (String, Sensitive)
+- `audience` (String) WIF audience URL for GCP workload identity federation.
+
+Optional:
+
+- `service_account_email` (String) Optional GCP service account email to impersonate via workload identity federation.
## Import
diff --git a/internal/provider/export_bigquery.go b/internal/provider/export_bigquery.go
index 32c58d8..b36df90 100644
--- a/internal/provider/export_bigquery.go
+++ b/internal/provider/export_bigquery.go
@@ -7,6 +7,7 @@ import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -21,6 +22,7 @@ import (
// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &ExportBigQueryResource{}
+var _ resource.ResourceWithConfigValidators = &ExportBigQueryResource{}
func NewMondooExportBigQueryResource() resource.Resource {
return &ExportBigQueryResource{}
@@ -42,7 +44,31 @@ type BigQueryExportResourceModel struct {
WifSubject types.String `tfsdk:"wif_subject"`
// credentials
- ServiceAccountKey types.String `tfsdk:"service_account_key"`
+ ServiceAccountKey types.String `tfsdk:"service_account_key"`
+ Credentials *exportBigQueryCredentialsWrapper `tfsdk:"credentials"`
+}
+
+type exportBigQueryCredentialsWrapper struct {
+ Wif *gcpWifCredentialModel `tfsdk:"wif"`
+}
+
+func (m BigQueryExportResourceModel) GetConfigurationOptions() *mondoov1.BigqueryConfigurationOptionsInput {
+ opts := &mondoov1.BigqueryConfigurationOptionsInput{
+ DatasetId: mondoov1.String(m.DatasetID.ValueString()),
+ }
+
+ if !m.ServiceAccountKey.IsNull() && !m.ServiceAccountKey.IsUnknown() {
+ opts.ServiceAccount = mondoov1.NewStringPtr(mondoov1.String(m.ServiceAccountKey.ValueString()))
+ }
+
+ if m.Credentials != nil && m.Credentials.Wif != nil {
+ opts.WifAudience = mondoov1.NewStringPtr(mondoov1.String(m.Credentials.Wif.Audience.ValueString()))
+ if !m.Credentials.Wif.ServiceAccountEmail.IsNull() && !m.Credentials.Wif.ServiceAccountEmail.IsUnknown() {
+ opts.WifServiceAccountEmail = mondoov1.NewStringPtr(mondoov1.String(m.Credentials.Wif.ServiceAccountEmail.ValueString()))
+ }
+ }
+
+ return opts
}
func (r *ExportBigQueryResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
@@ -109,11 +135,28 @@ func (r *ExportBigQueryResource) Schema(ctx context.Context, req resource.Schema
},
},
"service_account_key": schema.StringAttribute{
- MarkdownDescription: "Google service account JSON key content.",
- Required: true,
+ MarkdownDescription: "Google service account JSON key content. Mutually exclusive with `credentials.wif`.",
+ Optional: true,
Sensitive: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.RequiresReplace(),
+ },
+ "credentials": schema.SingleNestedAttribute{
+ MarkdownDescription: "Credentials for the BigQuery export. Provide `wif` for workload identity federation instead of the top-level `service_account_key`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "wif": schema.SingleNestedAttribute{
+ MarkdownDescription: "Workload identity federation configuration. Mutually exclusive with `service_account_key`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "audience": schema.StringAttribute{
+ MarkdownDescription: "WIF audience URL for GCP workload identity federation.",
+ Required: true,
+ },
+ "service_account_email": schema.StringAttribute{
+ MarkdownDescription: "Optional GCP service account email to impersonate via workload identity federation.",
+ Optional: true,
+ },
+ },
+ },
},
},
"wif_subject": schema.StringAttribute{
@@ -127,6 +170,15 @@ func (r *ExportBigQueryResource) Schema(ctx context.Context, req resource.Schema
}
}
+func (r *ExportBigQueryResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator {
+ return []resource.ConfigValidator{
+ resourcevalidator.ExactlyOneOf(
+ path.MatchRoot("service_account_key"),
+ path.MatchRoot("credentials").AtName("wif"),
+ ),
+ }
+}
+
func (r *ExportBigQueryResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
@@ -157,10 +209,7 @@ func (r *ExportBigQueryResource) Create(ctx context.Context, req resource.Create
}
configOpts := mondoov1.ClientIntegrationConfigurationInput{
- BigqueryConfigurationOptions: &mondoov1.BigqueryConfigurationOptionsInput{
- DatasetId: mondoov1.String(data.DatasetID.ValueString()),
- ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.ServiceAccountKey.ValueString())),
- },
+ BigqueryConfigurationOptions: data.GetConfigurationOptions(),
}
var integration *CreateClientIntegrationPayload
@@ -248,8 +297,13 @@ func (r *ExportBigQueryResource) Read(ctx context.Context, req resource.ReadRequ
}
// Update the state with the latest information
+ opts := integration.ConfigurationOptions.BigqueryConfigurationOptions
data.Name = types.StringValue(integration.Name)
- data.WifSubject = types.StringValue(integration.ConfigurationOptions.BigqueryConfigurationOptions.WifSubject)
+ data.WifSubject = types.StringValue(opts.WifSubject)
+ if data.Credentials != nil && data.Credentials.Wif != nil {
+ data.Credentials.Wif.Audience = types.StringValue(opts.WifAudience)
+ data.Credentials.Wif.ServiceAccountEmail = stringOrNull(opts.WifServiceAccountEmail)
+ }
// Note: We don't update service_account_key to avoid showing sensitive data
// Save updated data into Terraform state
@@ -271,10 +325,7 @@ func (r *ExportBigQueryResource) Update(ctx context.Context, req resource.Update
data.Name.ValueString(),
mondoov1.ClientIntegrationTypeBigquery,
mondoov1.ClientIntegrationConfigurationInput{
- BigqueryConfigurationOptions: &mondoov1.BigqueryConfigurationOptionsInput{
- DatasetId: mondoov1.String(data.DatasetID.ValueString()),
- ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.ServiceAccountKey.ValueString())),
- },
+ BigqueryConfigurationOptions: data.GetConfigurationOptions(),
})
if err != nil {
@@ -308,14 +359,23 @@ func (r *ExportBigQueryResource) ImportState(ctx context.Context, req resource.I
return
}
+ opts := integration.ConfigurationOptions.BigqueryConfigurationOptions
model := BigQueryExportResourceModel{
Mrn: types.StringValue(integration.Mrn),
Name: types.StringValue(integration.Name),
ScopeMrn: types.StringValue(integration.ScopeMRN()),
- DatasetID: types.StringValue(integration.ConfigurationOptions.BigqueryConfigurationOptions.DatasetId),
- WifSubject: types.StringValue(integration.ConfigurationOptions.BigqueryConfigurationOptions.WifSubject),
+ DatasetID: types.StringValue(opts.DatasetId),
+ WifSubject: types.StringValue(opts.WifSubject),
ServiceAccountKey: types.StringPointerValue(nil), // Don't expose sensitive data
}
+ if opts.WifAudience != "" {
+ model.Credentials = &exportBigQueryCredentialsWrapper{
+ Wif: &gcpWifCredentialModel{
+ Audience: types.StringValue(opts.WifAudience),
+ ServiceAccountEmail: stringOrNull(opts.WifServiceAccountEmail),
+ },
+ }
+ }
if integration.IsSpaceScoped() {
model.SpaceID = types.StringValue(integration.SpaceID())
diff --git a/internal/provider/export_gcs_bucket.go b/internal/provider/export_gcs_bucket.go
index c95625e..b1c6840 100644
--- a/internal/provider/export_gcs_bucket.go
+++ b/internal/provider/export_gcs_bucket.go
@@ -8,6 +8,7 @@ import (
"fmt"
"strings"
+ "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -23,6 +24,7 @@ import (
// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &ExportGcsBucketResource{}
+var _ resource.ResourceWithConfigValidators = &ExportGcsBucketResource{}
func NewMondooExportGSCBucketResource() resource.Resource {
return &ExportGcsBucketResource{}
@@ -45,7 +47,37 @@ type ExportGcsBucketResourceModel struct {
WifSubject types.String `tfsdk:"wif_subject"`
// credentials
- Credential gcsBucketExportCredentialModel `tfsdk:"credentials"`
+ Credential exportGcsBucketCredentialModel `tfsdk:"credentials"`
+}
+
+type exportGcsBucketCredentialModel struct {
+ PrivateKey types.String `tfsdk:"private_key"`
+ Wif *gcpWifCredentialModel `tfsdk:"wif"`
+}
+
+func (m ExportGcsBucketResourceModel) GetConfigurationOptions() *mondoov1.GcsBucketConfigurationOptionsInput {
+ outputFormat := mondoov1.BucketOutputTypeJsonl
+ if strings.ToLower(m.ExportFormat.ValueString()) == "csv" {
+ outputFormat = mondoov1.BucketOutputTypeCsv
+ }
+
+ opts := &mondoov1.GcsBucketConfigurationOptionsInput{
+ Output: outputFormat,
+ Bucket: mondoov1.String(m.BucketName.ValueString()),
+ }
+
+ if !m.Credential.PrivateKey.IsNull() && !m.Credential.PrivateKey.IsUnknown() {
+ opts.ServiceAccount = mondoov1.NewStringPtr(mondoov1.String(m.Credential.PrivateKey.ValueString()))
+ }
+
+ if m.Credential.Wif != nil {
+ opts.WifAudience = mondoov1.NewStringPtr(mondoov1.String(m.Credential.Wif.Audience.ValueString()))
+ if !m.Credential.Wif.ServiceAccountEmail.IsNull() && !m.Credential.Wif.ServiceAccountEmail.IsUnknown() {
+ opts.WifServiceAccountEmail = mondoov1.NewStringPtr(mondoov1.String(m.Credential.Wif.ServiceAccountEmail.ValueString()))
+ }
+ }
+
+ return opts
}
func (r *ExportGcsBucketResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
@@ -132,20 +164,43 @@ func (r *ExportGcsBucketResource) Schema(ctx context.Context, req resource.Schem
},
},
"credentials": schema.SingleNestedAttribute{
- MarkdownDescription: "Credentials for the Google Cloud Storage bucket.",
+ MarkdownDescription: "Credentials for the Google Cloud Storage bucket. Provide either a static service account `private_key` or a `wif` block for workload identity federation.",
Required: true,
Attributes: map[string]schema.Attribute{
"private_key": schema.StringAttribute{
- MarkdownDescription: "Private key for the service account in JSON format.",
- Required: true,
+ MarkdownDescription: "Private key for the service account in JSON format. Mutually exclusive with `wif`.",
+ Optional: true,
Sensitive: true,
},
+ "wif": schema.SingleNestedAttribute{
+ MarkdownDescription: "Workload identity federation configuration. Mutually exclusive with `private_key`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "audience": schema.StringAttribute{
+ MarkdownDescription: "WIF audience URL for GCP workload identity federation.",
+ Required: true,
+ },
+ "service_account_email": schema.StringAttribute{
+ MarkdownDescription: "Optional GCP service account email to impersonate via workload identity federation.",
+ Optional: true,
+ },
+ },
+ },
},
},
},
}
}
+func (r *ExportGcsBucketResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator {
+ return []resource.ConfigValidator{
+ resourcevalidator.ExactlyOneOf(
+ path.MatchRoot("credentials").AtName("private_key"),
+ path.MatchRoot("credentials").AtName("wif"),
+ ),
+ }
+}
+
func (r *ExportGcsBucketResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
@@ -175,18 +230,8 @@ func (r *ExportGcsBucketResource) Create(ctx context.Context, req resource.Creat
return
}
- // Determine output format
- outputFormat := mondoov1.BucketOutputTypeJsonl
- if strings.ToLower(data.ExportFormat.ValueString()) == "csv" {
- outputFormat = mondoov1.BucketOutputTypeCsv
- }
-
configOpts := mondoov1.ClientIntegrationConfigurationInput{
- GcsBucketConfigurationOptions: &mondoov1.GcsBucketConfigurationOptionsInput{
- Output: outputFormat,
- Bucket: mondoov1.String(data.BucketName.ValueString()),
- ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PrivateKey.ValueString())),
- },
+ GcsBucketConfigurationOptions: data.GetConfigurationOptions(),
}
var integration *CreateClientIntegrationPayload
@@ -273,7 +318,12 @@ func (r *ExportGcsBucketResource) Read(ctx context.Context, req resource.ReadReq
resp.Diagnostics.AddError("Error reading GCS bucket export integration", err.Error())
return
}
- data.WifSubject = types.StringValue(integration.ConfigurationOptions.GcsBucketConfigurationOptions.WifSubject)
+ opts := integration.ConfigurationOptions.GcsBucketConfigurationOptions
+ data.WifSubject = types.StringValue(opts.WifSubject)
+ if data.Credential.Wif != nil {
+ data.Credential.Wif.Audience = types.StringValue(opts.WifAudience)
+ data.Credential.Wif.ServiceAccountEmail = stringOrNull(opts.WifServiceAccountEmail)
+ }
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -288,23 +338,13 @@ func (r *ExportGcsBucketResource) Update(ctx context.Context, req resource.Updat
return
}
- // Determine output format
- outputFormat := mondoov1.BucketOutputTypeJsonl
- if strings.ToLower(data.ExportFormat.ValueString()) == "csv" {
- outputFormat = mondoov1.BucketOutputTypeCsv
- }
-
// Do GraphQL request to API to update the resource.
_, err := r.client.UpdateIntegration(ctx,
data.Mrn.ValueString(),
data.Name.ValueString(),
mondoov1.ClientIntegrationTypeGcsBucket,
mondoov1.ClientIntegrationConfigurationInput{
- GcsBucketConfigurationOptions: &mondoov1.GcsBucketConfigurationOptionsInput{
- Output: outputFormat,
- Bucket: mondoov1.String(data.BucketName.ValueString()),
- ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PrivateKey.ValueString())),
- },
+ GcsBucketConfigurationOptions: data.GetConfigurationOptions(),
})
if err != nil {
@@ -337,18 +377,25 @@ func (r *ExportGcsBucketResource) ImportState(ctx context.Context, req resource.
return
}
+ opts := integration.ConfigurationOptions.GcsBucketConfigurationOptions
model := ExportGcsBucketResourceModel{
Mrn: types.StringValue(integration.Mrn),
Name: types.StringValue(integration.Name),
ScopeMrn: types.StringValue(integration.ScopeMRN()),
- BucketName: types.StringValue(integration.ConfigurationOptions.GcsBucketConfigurationOptions.Bucket),
- ExportFormat: types.StringValue(integration.ConfigurationOptions.GcsBucketConfigurationOptions.Output),
- WifSubject: types.StringValue(integration.ConfigurationOptions.GcsBucketConfigurationOptions.WifSubject),
+ BucketName: types.StringValue(opts.Bucket),
+ ExportFormat: types.StringValue(opts.Output),
+ WifSubject: types.StringValue(opts.WifSubject),
- Credential: gcsBucketExportCredentialModel{
+ Credential: exportGcsBucketCredentialModel{
PrivateKey: types.StringPointerValue(nil),
},
}
+ if opts.WifAudience != "" {
+ model.Credential.Wif = &gcpWifCredentialModel{
+ Audience: types.StringValue(opts.WifAudience),
+ ServiceAccountEmail: stringOrNull(opts.WifServiceAccountEmail),
+ }
+ }
if integration.IsSpaceScoped() {
model.SpaceID = types.StringValue(integration.SpaceID())
diff --git a/internal/provider/gql.go b/internal/provider/gql.go
index 4ba9151..f105b2a 100644
--- a/internal/provider/gql.go
+++ b/internal/provider/gql.go
@@ -839,9 +839,11 @@ type GithubConfigurationOptions struct {
}
type GcsBucketConfigurationOptions struct {
- Bucket string
- Output string
- WifSubject string
+ Bucket string
+ Output string
+ WifAudience string
+ WifServiceAccountEmail string
+ WifSubject string
}
type AwsS3ConfigurationOptions struct {
@@ -851,8 +853,10 @@ type AwsS3ConfigurationOptions struct {
}
type BigqueryConfigurationOptions struct {
- DatasetId string
- WifSubject string
+ DatasetId string
+ WifAudience string
+ WifServiceAccountEmail string
+ WifSubject string
}
type GitlabConfigurationOptions struct {
@@ -877,13 +881,17 @@ type MsIntuneConfigurationOptions struct {
type HostedAwsConfigurationOptions struct {
AccessKeyId string
Role string
+ WifAudience string
+ WifRoleArn string
WifSubject string
}
type GcpConfigurationOptions struct {
- ProjectId string
- DiscoverAll bool
- WifSubject string
+ ProjectId string
+ DiscoverAll bool
+ WifAudience string
+ WifServiceAccountEmail string
+ WifSubject string
}
type ShodanConfigurationOptions struct {
diff --git a/internal/provider/integration_aws_resource.go b/internal/provider/integration_aws_resource.go
index c3a1a6c..389d3f2 100644
--- a/internal/provider/integration_aws_resource.go
+++ b/internal/provider/integration_aws_resource.go
@@ -8,7 +8,7 @@ import (
"fmt"
"regexp"
- "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -24,6 +24,7 @@ import (
// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = (*integrationAwsResource)(nil)
var _ resource.ResourceWithImportState = (*integrationAwsResource)(nil)
+var _ resource.ResourceWithConfigValidators = (*integrationAwsResource)(nil)
func NewIntegrationAwsResource() resource.Resource {
return &integrationAwsResource{}
@@ -49,6 +50,7 @@ type integrationAwsResourceModel struct {
type integrationAwsCredentialModel struct {
Role *roleCredentialModel `tfsdk:"role"`
Key *accessKeyCredentialModel `tfsdk:"key"`
+ Wif *awsWifCredentialModel `tfsdk:"wif"`
}
type roleCredentialModel struct {
@@ -61,6 +63,11 @@ type accessKeyCredentialModel struct {
SecretKey types.String `tfsdk:"secret_key"`
}
+type awsWifCredentialModel struct {
+ Audience types.String `tfsdk:"audience"`
+ RoleArn types.String `tfsdk:"role_arn"`
+}
+
func (m integrationAwsResourceModel) GetConfigurationOptions() *mondoov1.HostedAwsConfigurationOptionsInput {
opts := &mondoov1.HostedAwsConfigurationOptionsInput{}
@@ -84,6 +91,13 @@ func (m integrationAwsResourceModel) GetConfigurationOptions() *mondoov1.HostedA
}
}
+ if m.Credential.Wif != nil {
+ opts.WifCredential = &mondoov1.AWSWifCredential{
+ Audience: mondoov1.String(m.Credential.Wif.Audience.ValueString()),
+ RoleArn: mondoov1.String(m.Credential.Wif.RoleArn.ValueString()),
+ }
+ }
+
return opts
}
@@ -125,10 +139,12 @@ func (r *integrationAwsResource) Schema(ctx context.Context, req resource.Schema
},
},
"credentials": schema.SingleNestedAttribute{
- Required: true,
+ MarkdownDescription: "Credentials for the AWS integration. Exactly one of `role`, `key`, or `wif` must be configured.",
+ Required: true,
Attributes: map[string]schema.Attribute{
"role": schema.SingleNestedAttribute{
- Optional: true,
+ MarkdownDescription: "IAM role credentials. Mutually exclusive with `key` and `wif`.",
+ Optional: true,
Attributes: map[string]schema.Attribute{
"role_arn": schema.StringAttribute{
Required: true,
@@ -139,15 +155,10 @@ func (r *integrationAwsResource) Schema(ctx context.Context, req resource.Schema
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,
+ MarkdownDescription: "Static IAM access key credentials. Mutually exclusive with `role` and `wif`.",
+ Optional: true,
Attributes: map[string]schema.Attribute{
"access_key": schema.StringAttribute{
Required: true,
@@ -171,12 +182,36 @@ func (r *integrationAwsResource) Schema(ctx context.Context, req resource.Schema
},
},
},
+ "wif": schema.SingleNestedAttribute{
+ MarkdownDescription: "Workload identity federation credentials. Uses Mondoo as an OIDC identity provider to assume an IAM role via web identity. Mutually exclusive with `role` and `key`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "audience": schema.StringAttribute{
+ MarkdownDescription: "Audience value configured in the AWS IAM OIDC identity provider.",
+ Required: true,
+ },
+ "role_arn": schema.StringAttribute{
+ MarkdownDescription: "ARN of the IAM role to assume via web identity federation.",
+ Required: true,
+ },
+ },
+ },
},
},
},
}
}
+func (r *integrationAwsResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator {
+ return []resource.ConfigValidator{
+ resourcevalidator.ExactlyOneOf(
+ path.MatchRoot("credentials").AtName("role"),
+ path.MatchRoot("credentials").AtName("key"),
+ path.MatchRoot("credentials").AtName("wif"),
+ ),
+ }
+}
+
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 {
@@ -267,7 +302,12 @@ func (r *integrationAwsResource) Read(ctx context.Context, req resource.ReadRequ
resp.Diagnostics.AddError("Error reading AWS integration", err.Error())
return
}
- data.WifSubject = types.StringValue(integration.ConfigurationOptions.HostedAwsConfigurationOptions.WifSubject)
+ opts := integration.ConfigurationOptions.HostedAwsConfigurationOptions
+ data.WifSubject = types.StringValue(opts.WifSubject)
+ if data.Credential.Wif != nil {
+ data.Credential.Wif.Audience = types.StringValue(opts.WifAudience)
+ data.Credential.Wif.RoleArn = types.StringValue(opts.WifRoleArn)
+ }
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -333,21 +373,30 @@ func (r *integrationAwsResource) ImportState(ctx context.Context, req resource.I
return
}
+ opts := integration.ConfigurationOptions.HostedAwsConfigurationOptions
model := integrationAwsResourceModel{
SpaceID: types.StringValue(integration.SpaceID()),
Mrn: types.StringValue(integration.Mrn),
Name: types.StringValue(integration.Name),
- WifSubject: types.StringValue(integration.ConfigurationOptions.HostedAwsConfigurationOptions.WifSubject),
- Credential: integrationAwsCredentialModel{
- Role: &roleCredentialModel{
- RoleArn: types.StringValue(integration.ConfigurationOptions.HostedAwsConfigurationOptions.Role),
- ExternalId: types.StringPointerValue(nil), // cannot be imported
- },
- Key: &accessKeyCredentialModel{
- AccessKey: types.StringValue(integration.ConfigurationOptions.HostedAwsConfigurationOptions.AccessKeyId),
- SecretKey: types.StringPointerValue(nil), // cannot be imported
- },
- },
+ WifSubject: types.StringValue(opts.WifSubject),
+ }
+
+ switch {
+ case opts.WifAudience != "" && opts.WifRoleArn != "":
+ model.Credential.Wif = &awsWifCredentialModel{
+ Audience: types.StringValue(opts.WifAudience),
+ RoleArn: types.StringValue(opts.WifRoleArn),
+ }
+ case opts.AccessKeyId != "":
+ model.Credential.Key = &accessKeyCredentialModel{
+ AccessKey: types.StringValue(opts.AccessKeyId),
+ SecretKey: types.StringPointerValue(nil), // cannot be imported
+ }
+ case opts.Role != "":
+ model.Credential.Role = &roleCredentialModel{
+ RoleArn: types.StringValue(opts.Role),
+ ExternalId: types.StringPointerValue(nil), // cannot be imported
+ }
}
resp.State.Set(ctx, &model)
diff --git a/internal/provider/integration_gcp_resource.go b/internal/provider/integration_gcp_resource.go
index 1d71b7f..398e60e 100644
--- a/internal/provider/integration_gcp_resource.go
+++ b/internal/provider/integration_gcp_resource.go
@@ -7,7 +7,9 @@ import (
"context"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "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"
@@ -21,6 +23,7 @@ import (
// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = (*integrationGcpResource)(nil)
var _ resource.ResourceWithImportState = (*integrationGcpResource)(nil)
+var _ resource.ResourceWithConfigValidators = (*integrationGcpResource)(nil)
func NewIntegrationGcpResource() resource.Resource {
return &integrationGcpResource{}
@@ -45,7 +48,40 @@ type integrationGcpResourceModel struct {
}
type integrationGcpCredentialModel struct {
- PrivateKey types.String `tfsdk:"private_key"`
+ PrivateKey types.String `tfsdk:"private_key"`
+ Wif *gcpWifCredentialModel `tfsdk:"wif"`
+}
+
+type gcpWifCredentialModel struct {
+ Audience types.String `tfsdk:"audience"`
+ ServiceAccountEmail types.String `tfsdk:"service_account_email"`
+}
+
+func stringOrNull(s string) types.String {
+ if s == "" {
+ return types.StringNull()
+ }
+ return types.StringValue(s)
+}
+
+func (m integrationGcpResourceModel) GetConfigurationOptions() *mondoov1.GcpConfigurationOptionsInput {
+ opts := &mondoov1.GcpConfigurationOptionsInput{
+ ProjectId: mondoov1.NewStringPtr(mondoov1.String(m.ProjectId.ValueString())),
+ DiscoverAll: mondoov1.NewBooleanPtr(mondoov1.Boolean(true)),
+ }
+
+ if !m.Credential.PrivateKey.IsNull() && !m.Credential.PrivateKey.IsUnknown() {
+ opts.ServiceAccount = mondoov1.NewStringPtr(mondoov1.String(m.Credential.PrivateKey.ValueString()))
+ }
+
+ if m.Credential.Wif != nil {
+ opts.WifAudience = mondoov1.NewStringPtr(mondoov1.String(m.Credential.Wif.Audience.ValueString()))
+ if !m.Credential.Wif.ServiceAccountEmail.IsNull() && !m.Credential.Wif.ServiceAccountEmail.IsUnknown() {
+ opts.WifServiceAccountEmail = mondoov1.NewStringPtr(mondoov1.String(m.Credential.Wif.ServiceAccountEmail.ValueString()))
+ }
+ }
+
+ return opts
}
func (r *integrationGcpResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
@@ -90,11 +126,27 @@ func (r *integrationGcpResource) Schema(ctx context.Context, req resource.Schema
},
},
"credentials": schema.SingleNestedAttribute{
- Required: true,
+ MarkdownDescription: "Credentials for the GCP integration. Provide either a static service account `private_key` or a `wif` block for workload identity federation.",
+ Required: true,
Attributes: map[string]schema.Attribute{
"private_key": schema.StringAttribute{
- Required: true,
- Sensitive: true,
+ MarkdownDescription: "GCP service account JSON key. Mutually exclusive with `wif`.",
+ Optional: true,
+ Sensitive: true,
+ },
+ "wif": schema.SingleNestedAttribute{
+ MarkdownDescription: "Workload identity federation configuration. Mutually exclusive with `private_key`.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "audience": schema.StringAttribute{
+ MarkdownDescription: "WIF audience URL for GCP workload identity federation.",
+ Required: true,
+ },
+ "service_account_email": schema.StringAttribute{
+ MarkdownDescription: "Optional GCP service account email to impersonate via workload identity federation.",
+ Optional: true,
+ },
+ },
},
},
},
@@ -102,6 +154,15 @@ func (r *integrationGcpResource) Schema(ctx context.Context, req resource.Schema
}
}
+func (r *integrationGcpResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator {
+ return []resource.ConfigValidator{
+ resourcevalidator.ExactlyOneOf(
+ path.MatchRoot("credentials").AtName("private_key"),
+ path.MatchRoot("credentials").AtName("wif"),
+ ),
+ }
+}
+
func (r *integrationGcpResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
@@ -147,11 +208,7 @@ func (r *integrationGcpResource) Create(ctx context.Context, req resource.Create
data.Name.ValueString(),
mondoov1.ClientIntegrationTypeGcp,
mondoov1.ClientIntegrationConfigurationInput{
- GcpConfigurationOptions: &mondoov1.GcpConfigurationOptionsInput{
- ProjectId: mondoov1.NewStringPtr(mondoov1.String(data.ProjectId.ValueString())),
- ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PrivateKey.ValueString())),
- DiscoverAll: mondoov1.NewBooleanPtr(mondoov1.Boolean(true)),
- },
+ GcpConfigurationOptions: data.GetConfigurationOptions(),
})
if err != nil {
resp.Diagnostics.
@@ -207,7 +264,12 @@ func (r *integrationGcpResource) Read(ctx context.Context, req resource.ReadRequ
resp.Diagnostics.AddError("Error reading GCP integration", err.Error())
return
}
- data.WifSubject = types.StringValue(integration.ConfigurationOptions.GcpConfigurationOptions.WifSubject)
+ opts := integration.ConfigurationOptions.GcpConfigurationOptions
+ data.WifSubject = types.StringValue(opts.WifSubject)
+ if data.Credential.Wif != nil {
+ data.Credential.Wif.Audience = types.StringValue(opts.WifAudience)
+ data.Credential.Wif.ServiceAccountEmail = stringOrNull(opts.WifServiceAccountEmail)
+ }
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -225,11 +287,7 @@ func (r *integrationGcpResource) Update(ctx context.Context, req resource.Update
// Do GraphQL request to API to update the resource.
opts := mondoov1.ClientIntegrationConfigurationInput{
- GcpConfigurationOptions: &mondoov1.GcpConfigurationOptionsInput{
- ProjectId: mondoov1.NewStringPtr(mondoov1.String(data.ProjectId.ValueString())),
- ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PrivateKey.ValueString())),
- DiscoverAll: mondoov1.NewBooleanPtr(mondoov1.Boolean(true)),
- },
+ GcpConfigurationOptions: data.GetConfigurationOptions(),
}
_, err := r.client.UpdateIntegration(ctx,
@@ -278,16 +336,23 @@ func (r *integrationGcpResource) ImportState(ctx context.Context, req resource.I
return
}
+ opts := integration.ConfigurationOptions.GcpConfigurationOptions
model := integrationGcpResourceModel{
Mrn: types.StringValue(integration.Mrn),
Name: types.StringValue(integration.Name),
SpaceID: types.StringValue(integration.SpaceID()),
- ProjectId: types.StringValue(integration.ConfigurationOptions.GcpConfigurationOptions.ProjectId),
- WifSubject: types.StringValue(integration.ConfigurationOptions.GcpConfigurationOptions.WifSubject),
+ ProjectId: types.StringValue(opts.ProjectId),
+ WifSubject: types.StringValue(opts.WifSubject),
Credential: integrationGcpCredentialModel{
PrivateKey: types.StringPointerValue(nil),
},
}
+ if opts.WifAudience != "" {
+ model.Credential.Wif = &gcpWifCredentialModel{
+ Audience: types.StringValue(opts.WifAudience),
+ ServiceAccountEmail: stringOrNull(opts.WifServiceAccountEmail),
+ }
+ }
resp.State.Set(ctx, &model)
}