diff --git a/docs/resources/integration_aws_parameter_store.md b/docs/resources/integration_aws_parameter_store.md index 30bf89a..949ad84 100644 --- a/docs/resources/integration_aws_parameter_store.md +++ b/docs/resources/integration_aws_parameter_store.md @@ -36,9 +36,14 @@ resource "infisical_integration_aws_parameter_store" "parameter-store-integratio secret_path = "" // example, /folder, or / parameter_store_path = "/example/secrets" - aws_region = "" // example, us-east-2 + aws_region = "" // example, us-east-2 + + # AWS Authentication access_key_id = "" secret_access_key = "" + # OR + assume_role_arn = "arn:aws:iam:::role/" + // Optional options = { @@ -58,17 +63,18 @@ resource "infisical_integration_aws_parameter_store" "parameter-store-integratio ### Required -- `access_key_id` (String, Sensitive) The AWS access key ID. Used to authenticate with AWS Secrets Manager. - `aws_region` (String) The AWS region to sync secrets to. (us-east-1, us-east-2, etc) - `environment` (String) The slug of the environment to sync to AWS Parameter Store (prod, dev, staging, etc). - `parameter_store_path` (String) The path in AWS Parameter Store to sync secrets to. - `project_id` (String) The ID of your Infisical project. -- `secret_access_key` (String, Sensitive) The AWS secret access key. Used to authenticate with AWS Secrets Manager. - `secret_path` (String) The secret path in Infisical to sync secrets from. ### Optional +- `access_key_id` (String, Sensitive) The AWS access key ID. Used to authenticate with AWS Parameter Store. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role. +- `assume_role_arn` (String) The ARN of the role to assume when syncing secrets to AWS Parameter Store. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role. - `options` (Attributes) Integration options (see [below for nested schema](#nestedatt--options)) +- `secret_access_key` (String, Sensitive) The AWS secret access key. Used to authenticate with AWS Parameter Store. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role. ### Read-Only diff --git a/docs/resources/integration_aws_secrets_manager.md b/docs/resources/integration_aws_secrets_manager.md index 4664d42..4676129 100644 --- a/docs/resources/integration_aws_secrets_manager.md +++ b/docs/resources/integration_aws_secrets_manager.md @@ -38,8 +38,11 @@ resource "infisical_integration_aws_secrets_manager" "secrets-manager-integratio secrets_manager_path = "/example/secrets" # Only required if mapping_behavior is one-to-one mapping_behavior = "one-to-one" # Optional, default is many-to-one + # AWS Authentication access_key_id = "" secret_access_key = "" + # OR + assume_role_arn = "arn:aws:iam:::role/" options = { secret_prefix = "" @@ -58,17 +61,18 @@ resource "infisical_integration_aws_secrets_manager" "secrets-manager-integratio ### Required -- `access_key_id` (String, Sensitive) The AWS access key ID. Used to authenticate with AWS Secrets Manager. - `aws_region` (String) The AWS region to sync secrets to. (us-east-1, us-east-2, etc) - `environment` (String) The slug of the environment to sync to AWS Secrets Manager (prod, dev, staging, etc). - `project_id` (String) The ID of your Infisical project. -- `secret_access_key` (String, Sensitive) The AWS secret access key. Used to authenticate with AWS Secrets Manager. - `secret_path` (String) The secret path in Infisical to sync secrets from. ### Optional +- `access_key_id` (String, Sensitive) The AWS access key ID. Used to authenticate with AWS Secrets Manager. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role. +- `assume_role_arn` (String) The ARN of the role to assume when syncing secrets to AWS Secrets Manager. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role. - `mapping_behavior` (String) The behavior of the mapping. Can be 'many-to-one' or 'one-to-one'. Many to One: All Infisical secrets will be mapped to a single AWS secret. One to One: Each Infisical secret will be mapped to its own AWS secret. - `options` (Attributes) Integration options (see [below for nested schema](#nestedatt--options)) +- `secret_access_key` (String, Sensitive) The AWS secret access key. Used to authenticate with AWS Secrets Manager. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role. - `secrets_manager_path` (String) The path in AWS Secrets Manager to sync secrets to. This is required if mapping_behavior is 'many-to-one'. ### Read-Only diff --git a/examples/resources/infisical_integration_aws_parameter_store/resource.tf b/examples/resources/infisical_integration_aws_parameter_store/resource.tf index 5dd4177..30162b2 100644 --- a/examples/resources/infisical_integration_aws_parameter_store/resource.tf +++ b/examples/resources/infisical_integration_aws_parameter_store/resource.tf @@ -21,9 +21,14 @@ resource "infisical_integration_aws_parameter_store" "parameter-store-integratio secret_path = "" // example, /folder, or / parameter_store_path = "/example/secrets" - aws_region = "" // example, us-east-2 + aws_region = "" // example, us-east-2 + + # AWS Authentication access_key_id = "" secret_access_key = "" + # OR + assume_role_arn = "arn:aws:iam:::role/" + // Optional options = { diff --git a/examples/resources/infisical_integration_aws_secrets_manager/resource.tf b/examples/resources/infisical_integration_aws_secrets_manager/resource.tf index 4887048..f4a49a2 100644 --- a/examples/resources/infisical_integration_aws_secrets_manager/resource.tf +++ b/examples/resources/infisical_integration_aws_secrets_manager/resource.tf @@ -23,8 +23,11 @@ resource "infisical_integration_aws_secrets_manager" "secrets-manager-integratio secrets_manager_path = "/example/secrets" # Only required if mapping_behavior is one-to-one mapping_behavior = "one-to-one" # Optional, default is many-to-one + # AWS Authentication access_key_id = "" secret_access_key = "" + # OR + assume_role_arn = "arn:aws:iam:::role/" options = { secret_prefix = "" diff --git a/internal/client/model.go b/internal/client/model.go index 57c33a4..1f83207 100644 --- a/internal/client/model.go +++ b/internal/client/model.go @@ -1543,11 +1543,12 @@ type RevokeIdentityKubernetesAuthResponse struct { } type CreateIntegrationAuthRequest struct { - AccessId string `json:"accessId"` - AccessToken string `json:"accessToken"` - RefreshToken string `json:"refreshToken"` - ProjectID string `json:"workspaceId"` - Integration IntegrationAuthType `json:"integration"` + AccessId string `json:"accessId,omitempty"` + AccessToken string `json:"accessToken,omitempty"` + AWSAssumeIamRoleArn string `json:"awsAssumeIamRoleArn,omitempty"` + RefreshToken string `json:"refreshToken,omitempty"` + ProjectID string `json:"workspaceId"` + Integration IntegrationAuthType `json:"integration"` URL string `json:"url"` } diff --git a/internal/pkg/input/model.go b/internal/pkg/input/model.go new file mode 100644 index 0000000..f1307ad --- /dev/null +++ b/internal/pkg/input/model.go @@ -0,0 +1,17 @@ +package pkg + +type AwsAuthenticationMethod string + +const ( + AwsAuthMethodAccessKey AwsAuthenticationMethod = "access_key" + AwsAuthMethodAssumeRole AwsAuthenticationMethod = "assume_role" +) + +type AwsAccessKeyCredentials struct { + AccessKeyId string + SecretAccessKey string +} + +type AwsAssumeRoleCredentials struct { + AssumeRoleArn string +} diff --git a/internal/pkg/input/validate-aws.go b/internal/pkg/input/validate-aws.go new file mode 100644 index 0000000..e3ca92e --- /dev/null +++ b/internal/pkg/input/validate-aws.go @@ -0,0 +1,31 @@ +package pkg + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func ValidateAwsInputCredentials(accessKeyId basetypes.StringValue, secretAccessKey basetypes.StringValue, assumeRoleArn basetypes.StringValue) (AwsAuthenticationMethod, error) { + + // No credentials provided at all + if assumeRoleArn.ValueString() == "" && (accessKeyId.ValueString() == "" && secretAccessKey.ValueString() == "") { + return "", fmt.Errorf("No credentials provided. Either set access_key_id and secret_access_key, or assume_role_arn.") + } + + if accessKeyId.ValueString() != "" && secretAccessKey.ValueString() != "" && assumeRoleArn.ValueString() != "" { + return "", fmt.Errorf("Both access_key_id and secret_access_key, and assume_role_arn are provided. Only one set of credentials can be used. Either set access_key_id and secret_access_key, or assume_role_arn.") + } + + // Access key and secret key provided + if accessKeyId.ValueString() != "" && secretAccessKey.ValueString() != "" { + return AwsAuthMethodAccessKey, nil + } + + // Assume role provided + if assumeRoleArn.ValueString() != "" { + return AwsAuthMethodAssumeRole, nil + } + + return "", fmt.Errorf("Invalid credentials provided. Either set access_key_id and secret_access_key, or assume_role_arn.") +} diff --git a/internal/provider/resource/integration_aws_parameter_store.go b/internal/provider/resource/integration_aws_parameter_store.go index 7eb3d0d..8a5dee5 100644 --- a/internal/provider/resource/integration_aws_parameter_store.go +++ b/internal/provider/resource/integration_aws_parameter_store.go @@ -4,6 +4,7 @@ import ( "context" "fmt" infisical "terraform-provider-infisical/internal/client" + pkg "terraform-provider-infisical/internal/pkg/input" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -45,6 +46,7 @@ type AwsParameterStoreOptions struct { type IntegrationAWSParameterStoreResourceModel struct { AccessKeyID types.String `tfsdk:"access_key_id"` SecretAccessKey types.String `tfsdk:"secret_access_key"` + AssumeRoleArn types.String `tfsdk:"assume_role_arn"` ProjectID types.String `tfsdk:"project_id"` IntegrationAuthID types.String `tfsdk:"integration_auth_id"` @@ -130,15 +132,21 @@ func (r *IntegrationAWSParameterStoreResource) Schema(_ context.Context, _ resou "access_key_id": schema.StringAttribute{ Sensitive: true, - Required: true, - Description: "The AWS access key ID. Used to authenticate with AWS Secrets Manager.", + Optional: true, + Description: "The AWS access key ID. Used to authenticate with AWS Parameter Store. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, "secret_access_key": schema.StringAttribute{ Sensitive: true, - Required: true, - Description: "The AWS secret access key. Used to authenticate with AWS Secrets Manager.", + Optional: true, + Description: "The AWS secret access key. Used to authenticate with AWS Parameter Store. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role.", + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + + "assume_role_arn": schema.StringAttribute{ + Optional: true, + Description: "The ARN of the role to assume when syncing secrets to AWS Parameter Store. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, @@ -205,13 +213,30 @@ func (r *IntegrationAWSParameterStoreResource) Create(ctx context.Context, req r return } - // Create integration auth first - auth, err := r.client.CreateIntegrationAuth(infisical.CreateIntegrationAuthRequest{ - AccessId: plan.AccessKeyID.ValueString(), - AccessToken: plan.SecretAccessKey.ValueString(), + authMethod, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) + + if err != nil { + resp.Diagnostics.AddError( + "Error validating AWS credentials", + err.Error(), + ) + return + } + + createIntegrationAuthRequest := infisical.CreateIntegrationAuthRequest{ ProjectID: plan.ProjectID.ValueString(), Integration: infisical.IntegrationAuthTypeAwsParameterStore, - }) + } + + if authMethod == pkg.AwsAuthMethodAccessKey { + createIntegrationAuthRequest.AccessId = plan.AccessKeyID.ValueString() + createIntegrationAuthRequest.AccessToken = plan.SecretAccessKey.ValueString() + } else if authMethod == pkg.AwsAuthMethodAssumeRole { + createIntegrationAuthRequest.AWSAssumeIamRoleArn = plan.AssumeRoleArn.ValueString() + } + + // Create integration auth first + auth, err := r.client.CreateIntegrationAuth(createIntegrationAuthRequest) if err != nil { resp.Diagnostics.AddError( @@ -367,6 +392,16 @@ func (r *IntegrationAWSParameterStoreResource) Update(ctx context.Context, req r return } + _, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) + + if err != nil { + resp.Diagnostics.AddError( + "Error validating AWS credentials", + err.Error(), + ) + return + } + var planOptions AwsParameterStoreOptions if !plan.Options.IsNull() { diff --git a/internal/provider/resource/integration_aws_secrets_manager.go b/internal/provider/resource/integration_aws_secrets_manager.go index f751625..55fcf63 100644 --- a/internal/provider/resource/integration_aws_secrets_manager.go +++ b/internal/provider/resource/integration_aws_secrets_manager.go @@ -4,6 +4,7 @@ import ( "context" "fmt" infisical "terraform-provider-infisical/internal/client" + pkg "terraform-provider-infisical/internal/pkg/input" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -45,7 +46,9 @@ type AwsSecretsManagerOptions struct { type IntegrationAWSSecretsManagerResourceModel struct { AccessKeyID types.String `tfsdk:"access_key_id"` SecretAccessKey types.String `tfsdk:"secret_access_key"` - ProjectID types.String `tfsdk:"project_id"` + AssumeRoleArn types.String `tfsdk:"assume_role_arn"` + + ProjectID types.String `tfsdk:"project_id"` IntegrationAuthID types.String `tfsdk:"integration_auth_id"` IntegrationID types.String `tfsdk:"integration_id"` @@ -134,15 +137,21 @@ func (r *IntegrationAWSSecretsManagerResource) Schema(_ context.Context, _ resou "access_key_id": schema.StringAttribute{ Sensitive: true, - Required: true, - Description: "The AWS access key ID. Used to authenticate with AWS Secrets Manager.", + Optional: true, + Description: "The AWS access key ID. Used to authenticate with AWS Secrets Manager. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, "secret_access_key": schema.StringAttribute{ Sensitive: true, - Required: true, - Description: "The AWS secret access key. Used to authenticate with AWS Secrets Manager.", + Optional: true, + Description: "The AWS secret access key. Used to authenticate with AWS Secrets Manager. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role.", + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + + "assume_role_arn": schema.StringAttribute{ + Optional: true, + Description: "The ARN of the role to assume when syncing secrets to AWS Secrets Manager. You must either set secret_access_key and access_key_id, or set assume_role_arn to assume a role.", PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, @@ -232,13 +241,30 @@ func (r *IntegrationAWSSecretsManagerResource) Create(ctx context.Context, req r return } - // Create integration auth first - auth, err := r.client.CreateIntegrationAuth(infisical.CreateIntegrationAuthRequest{ - AccessId: plan.AccessKeyID.ValueString(), - AccessToken: plan.SecretAccessKey.ValueString(), + authMethod, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) + + if err != nil { + resp.Diagnostics.AddError( + "Error validating AWS credentials", + err.Error(), + ) + return + } + + createIntegrationAuthRequest := infisical.CreateIntegrationAuthRequest{ ProjectID: plan.ProjectID.ValueString(), Integration: infisical.IntegrationAuthTypeAwsSecretsManager, - }) + } + + if authMethod == pkg.AwsAuthMethodAccessKey { + createIntegrationAuthRequest.AccessId = plan.AccessKeyID.ValueString() + createIntegrationAuthRequest.AccessToken = plan.SecretAccessKey.ValueString() + } else if authMethod == pkg.AwsAuthMethodAssumeRole { + createIntegrationAuthRequest.AWSAssumeIamRoleArn = plan.AssumeRoleArn.ValueString() + } + + // Create integration auth first + auth, err := r.client.CreateIntegrationAuth(createIntegrationAuthRequest) if err != nil { resp.Diagnostics.AddError( @@ -408,6 +434,16 @@ func (r *IntegrationAWSSecretsManagerResource) Update(ctx context.Context, req r return } + _, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) + + if err != nil { + resp.Diagnostics.AddError( + "Error validating AWS credentials", + err.Error(), + ) + return + } + var planOptions AwsSecretsManagerOptions if !plan.Options.IsNull() {