diff --git a/docs/resources/integration_aws_parameter_store.md b/docs/resources/integration_aws_parameter_store.md index 949ad84..4e930aa 100644 --- a/docs/resources/integration_aws_parameter_store.md +++ b/docs/resources/integration_aws_parameter_store.md @@ -34,7 +34,7 @@ resource "infisical_integration_aws_parameter_store" "parameter-store-integratio environment = "" // example, dev secret_path = "" // example, /folder, or / - parameter_store_path = "/example/secrets" + parameter_store_path = "/example/secrets/" aws_region = "" // example, us-east-2 diff --git a/docs/resources/integration_aws_secrets_manager.md b/docs/resources/integration_aws_secrets_manager.md index 4676129..c9b5908 100644 --- a/docs/resources/integration_aws_secrets_manager.md +++ b/docs/resources/integration_aws_secrets_manager.md @@ -35,8 +35,8 @@ resource "infisical_integration_aws_secrets_manager" "secrets-manager-integratio secret_path = "" // example, /folder, or / - 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 + 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 = "" diff --git a/examples/resources/infisical_integration_aws_parameter_store/resource.tf b/examples/resources/infisical_integration_aws_parameter_store/resource.tf index 30162b2..04ddf82 100644 --- a/examples/resources/infisical_integration_aws_parameter_store/resource.tf +++ b/examples/resources/infisical_integration_aws_parameter_store/resource.tf @@ -19,7 +19,7 @@ resource "infisical_integration_aws_parameter_store" "parameter-store-integratio environment = "" // example, dev secret_path = "" // example, /folder, or / - parameter_store_path = "/example/secrets" + parameter_store_path = "/example/secrets/" aws_region = "" // example, us-east-2 @@ -40,4 +40,5 @@ resource "infisical_integration_aws_parameter_store" "parameter-store-integratio }, ] } -} \ No newline at end of file +} + diff --git a/examples/resources/infisical_integration_aws_secrets_manager/resource.tf b/examples/resources/infisical_integration_aws_secrets_manager/resource.tf index f4a49a2..9cc880e 100644 --- a/examples/resources/infisical_integration_aws_secrets_manager/resource.tf +++ b/examples/resources/infisical_integration_aws_secrets_manager/resource.tf @@ -20,8 +20,8 @@ resource "infisical_integration_aws_secrets_manager" "secrets-manager-integratio secret_path = "" // example, /folder, or / - 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 + 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 = "" @@ -38,4 +38,5 @@ resource "infisical_integration_aws_secrets_manager" "secrets-manager-integratio }, ] } -} \ No newline at end of file +} + diff --git a/internal/client/integrations_auth.go b/internal/client/integrations_auth.go index cb4fdc9..eea8adb 100644 --- a/internal/client/integrations_auth.go +++ b/internal/client/integrations_auth.go @@ -35,6 +35,26 @@ func (client Client) CreateIntegrationAuth(request CreateIntegrationAuthRequest) return body, nil } +func (client Client) UpdateIntegrationAuth(request UpdateIntegrationAuthRequest) (UpdateIntegrationAuthResponse, error) { + var body UpdateIntegrationAuthResponse + response, err := client.Config.HttpClient. + R(). + SetResult(&body). + SetHeader("User-Agent", USER_AGENT). + SetBody(request). + Patch("api/v1/integration-auth/" + request.IntegrationAuthId) + + if err != nil { + return UpdateIntegrationAuthResponse{}, fmt.Errorf("UpdateIntegrationAuth: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return UpdateIntegrationAuthResponse{}, fmt.Errorf("UpdateIntegrationAuth: Unsuccessful response. [response=%s]", string(response.Body())) + } + + return body, nil +} + // Deleting integration auth triggers a cascade effect, that will also delete the associated integration. func (client Client) DeleteIntegrationAuth(request DeleteIntegrationAuthRequest) (DeleteIntegrationAuthResponse, error) { var body DeleteIntegrationAuthResponse diff --git a/internal/client/model.go b/internal/client/model.go index 5e357d7..624b0ee 100644 --- a/internal/client/model.go +++ b/internal/client/model.go @@ -1552,12 +1552,28 @@ type CreateIntegrationAuthRequest struct { Integration IntegrationAuthType `json:"integration"` } +type UpdateIntegrationAuthRequest struct { + AccessId string `json:"accessId,omitempty"` + AccessToken string `json:"accessToken,omitempty"` + AWSAssumeIamRoleArn string `json:"awsAssumeIamRoleArn,omitempty"` + RefreshToken string `json:"refreshToken,omitempty"` + URL string `json:"url,omitempty"` + Integration IntegrationAuthType `json:"integration"` + IntegrationAuthId string `json:"integrationAuthId"` +} + type CreateIntegrationAuthResponse struct { IntegrationAuth struct { ID string `json:"id"` } `json:"integrationAuth"` } +type UpdateIntegrationAuthResponse struct { + IntegrationAuth struct { + ID string `json:"id"` + } `json:"integrationAuth"` +} + type DeleteIntegrationAuthRequest struct { ID string `json:"id"` } @@ -1661,6 +1677,8 @@ type UpdateIntegrationRequest struct { Environment string `json:"environment,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` IsActive bool `json:"isActive"` + Region string `json:"region,omitempty"` + Path string `json:"path,omitempty"` } type UpdateIntegrationResponse struct { diff --git a/internal/provider/resource/integration_aws_parameter_store.go b/internal/provider/resource/integration_aws_parameter_store.go index 8a5dee5..fa6c540 100644 --- a/internal/provider/resource/integration_aws_parameter_store.go +++ b/internal/provider/resource/integration_aws_parameter_store.go @@ -125,29 +125,25 @@ func (r *IntegrationAWSParameterStoreResource) Schema(_ context.Context, _ resou }, "aws_region": schema.StringAttribute{ - Required: true, - Description: "The AWS region to sync secrets to. (us-east-1, us-east-2, etc)", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Description: "The AWS region to sync secrets to. (us-east-1, us-east-2, etc)", }, "access_key_id": schema.StringAttribute{ - Sensitive: true, - 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()}, + Sensitive: true, + 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.", }, "secret_access_key": schema.StringAttribute{ - Sensitive: true, - 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()}, + Sensitive: true, + 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.", }, "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()}, + 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.", }, "project_id": schema.StringAttribute{ @@ -157,9 +153,8 @@ func (r *IntegrationAWSParameterStoreResource) Schema(_ context.Context, _ resou }, "parameter_store_path": schema.StringAttribute{ - Required: true, - Description: "The path in AWS Parameter Store to sync secrets to.", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Description: "The path in AWS Parameter Store to sync secrets to.", }, "environment": schema.StringAttribute{ @@ -214,7 +209,6 @@ func (r *IntegrationAWSParameterStoreResource) Create(ctx context.Context, req r } authMethod, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) - if err != nil { resp.Diagnostics.AddError( "Error validating AWS credentials", @@ -392,8 +386,7 @@ func (r *IntegrationAWSParameterStoreResource) Update(ctx context.Context, req r return } - _, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) - + authMethod, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) if err != nil { resp.Diagnostics.AddError( "Error validating AWS credentials", @@ -412,6 +405,26 @@ func (r *IntegrationAWSParameterStoreResource) Update(ctx context.Context, req r } } + updateIntegrationAuthRequest := infisical.UpdateIntegrationAuthRequest{ + Integration: infisical.IntegrationAuthTypeAwsSecretsManager, + IntegrationAuthId: plan.IntegrationAuthID.ValueString(), + } + if authMethod == pkg.AwsAuthMethodAccessKey { + updateIntegrationAuthRequest.AccessId = plan.AccessKeyID.ValueString() + updateIntegrationAuthRequest.AccessToken = plan.SecretAccessKey.ValueString() + } else if authMethod == pkg.AwsAuthMethodAssumeRole { + updateIntegrationAuthRequest.AWSAssumeIamRoleArn = plan.AssumeRoleArn.ValueString() + } + + _, err = r.client.UpdateIntegrationAuth(updateIntegrationAuthRequest) + if err != nil { + resp.Diagnostics.AddError( + "Error updating integration auth", + err.Error(), + ) + return + } + // Convert metadata to map[string]interface{} if needed metadataMap := map[string]interface{}{} @@ -428,6 +441,8 @@ func (r *IntegrationAWSParameterStoreResource) Update(ctx context.Context, req r Metadata: metadataMap, Environment: plan.Environment.ValueString(), SecretPath: plan.SecretPath.ValueString(), + Region: plan.AWSRegion.ValueString(), + Path: plan.AWSPath.ValueString(), }) if err != nil { diff --git a/internal/provider/resource/integration_aws_secrets_manager.go b/internal/provider/resource/integration_aws_secrets_manager.go index 55fcf63..36fbcd8 100644 --- a/internal/provider/resource/integration_aws_secrets_manager.go +++ b/internal/provider/resource/integration_aws_secrets_manager.go @@ -130,29 +130,25 @@ func (r *IntegrationAWSSecretsManagerResource) Schema(_ context.Context, _ resou }, "aws_region": schema.StringAttribute{ - Required: true, - Description: "The AWS region to sync secrets to. (us-east-1, us-east-2, etc)", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Description: "The AWS region to sync secrets to. (us-east-1, us-east-2, etc)", }, "access_key_id": schema.StringAttribute{ - Sensitive: true, - 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()}, + Sensitive: true, + 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.", }, "secret_access_key": schema.StringAttribute{ - Sensitive: true, - 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()}, + Sensitive: true, + 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.", }, "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()}, + 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.", }, "project_id": schema.StringAttribute{ @@ -174,9 +170,8 @@ func (r *IntegrationAWSSecretsManagerResource) Schema(_ context.Context, _ resou }, "secrets_manager_path": schema.StringAttribute{ - Optional: true, - Description: "The path in AWS Secrets Manager to sync secrets to. This is required if mapping_behavior is 'many-to-one'.", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Optional: true, + Description: "The path in AWS Secrets Manager to sync secrets to. This is required if mapping_behavior is 'many-to-one'.", }, "secret_path": schema.StringAttribute{ @@ -434,8 +429,38 @@ func (r *IntegrationAWSSecretsManagerResource) Update(ctx context.Context, req r return } - _, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) + var planOptions AwsSecretsManagerOptions + if !plan.Options.IsNull() { + diags := plan.Options.As(ctx, &planOptions, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + if plan.MappingBehavior.ValueString() == MAPPING_BEHAVIOR_MANY_TO_ONE && (plan.AWSPath.IsNull() || plan.AWSPath.ValueString() == "") { + resp.Diagnostics.AddError( + "Invalid plan", + "secrets_manager_path is required when mapping_behavior is 'many-to-one'", + ) + return + } + + if plan.MappingBehavior.ValueString() == MAPPING_BEHAVIOR_ONE_TO_ONE && (!plan.AWSPath.IsNull() && plan.AWSPath.ValueString() != "") { + resp.Diagnostics.AddError( + "Invalid plan", + "secrets_manager_path should not be used when mapping_behavior is 'one-to-one'", + ) + return + } + + updateIntegrationAuthRequest := infisical.UpdateIntegrationAuthRequest{ + Integration: infisical.IntegrationAuthTypeAwsSecretsManager, + IntegrationAuthId: plan.IntegrationAuthID.ValueString(), + } + + authMethod, err := pkg.ValidateAwsInputCredentials(plan.AccessKeyID, plan.SecretAccessKey, plan.AssumeRoleArn) if err != nil { resp.Diagnostics.AddError( "Error validating AWS credentials", @@ -444,14 +469,20 @@ func (r *IntegrationAWSSecretsManagerResource) Update(ctx context.Context, req r return } - var planOptions AwsSecretsManagerOptions + if authMethod == pkg.AwsAuthMethodAccessKey { + updateIntegrationAuthRequest.AccessId = plan.AccessKeyID.ValueString() + updateIntegrationAuthRequest.AccessToken = plan.SecretAccessKey.ValueString() + } else if authMethod == pkg.AwsAuthMethodAssumeRole { + updateIntegrationAuthRequest.AWSAssumeIamRoleArn = plan.AssumeRoleArn.ValueString() + } - if !plan.Options.IsNull() { - diags := plan.Options.As(ctx, &planOptions, basetypes.ObjectAsOptions{}) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } + _, err = r.client.UpdateIntegrationAuth(updateIntegrationAuthRequest) + if err != nil { + resp.Diagnostics.AddError( + "Error updating integration auth", + err.Error(), + ) + return } // Convert metadata to map[string]interface{} if needed @@ -468,13 +499,19 @@ func (r *IntegrationAWSSecretsManagerResource) Update(ctx context.Context, req r metadataMap["secretAWSTag"] = []infisical.AwsTag{} } - // Update the integration - updatedIntegration, err := r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ + updateIntegrationRequest := infisical.UpdateIntegrationRequest{ ID: state.IntegrationID.ValueString(), Metadata: metadataMap, Environment: plan.Environment.ValueString(), SecretPath: plan.SecretPath.ValueString(), - }) + Region: plan.AWSRegion.ValueString(), + } + if plan.MappingBehavior.ValueString() == MAPPING_BEHAVIOR_MANY_TO_ONE { + updateIntegrationRequest.App = plan.AWSPath.ValueString() + } + + // Update the integration + updatedIntegration, err := r.client.UpdateIntegration(updateIntegrationRequest) if err != nil { resp.Diagnostics.AddError( diff --git a/internal/provider/resource/integration_circleci.go b/internal/provider/resource/integration_circleci.go index 7c6aa67..dae3ba2 100644 --- a/internal/provider/resource/integration_circleci.go +++ b/internal/provider/resource/integration_circleci.go @@ -65,10 +65,9 @@ func (r *IntegrationCircleCIResource) Schema(_ context.Context, _ resource.Schem }, "circleci_token": schema.StringAttribute{ - Required: true, - Sensitive: true, - Description: "Your personal CircleCI token to authenticate with.", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Sensitive: true, + Description: "Your personal CircleCI token to authenticate with.", }, "project_id": schema.StringAttribute{ @@ -88,15 +87,13 @@ func (r *IntegrationCircleCIResource) Schema(_ context.Context, _ resource.Schem }, "circleci_org_slug": schema.StringAttribute{ - Required: true, - Description: "The organization slug of your CircleCI organization.", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Description: "The organization slug of your CircleCI organization.", }, "circleci_project_id": schema.StringAttribute{ - Required: true, - Description: "The project ID of your CircleCI project.", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Description: "The project ID of your CircleCI project.", }, }, } @@ -256,11 +253,27 @@ func (r *IntegrationCircleCIResource) Update(ctx context.Context, req resource.U return } + _, err := r.client.UpdateIntegrationAuth(infisical.UpdateIntegrationAuthRequest{ + Integration: infisical.IntegrationAuthTypeCircleCi, + IntegrationAuthId: plan.IntegrationAuthID.ValueString(), + AccessToken: plan.CircleCIToken.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError( + "Error updating integration auth", + err.Error(), + ) + return + } + // Update the integration - _, err := r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ + _, err = r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ ID: state.IntegrationID.ValueString(), Environment: plan.Environment.ValueString(), SecretPath: plan.SecretPath.ValueString(), + App: plan.CircleCIProjectID.ValueString(), // Needs to be the project slug + AppID: plan.CircleCIProjectID.ValueString(), // Needs to be the project ID + Owner: plan.CircleCIOrgSlug.ValueString(), // Needs to be the organization slug }) if err != nil { diff --git a/internal/provider/resource/integration_databricks.go b/internal/provider/resource/integration_databricks.go index 3350d45..767cd41 100644 --- a/internal/provider/resource/integration_databricks.go +++ b/internal/provider/resource/integration_databricks.go @@ -80,22 +80,19 @@ func (r *IntegrationDatabricksResource) Schema(_ context.Context, _ resource.Sch }, "databricks_host": schema.StringAttribute{ - Required: true, - Description: "The Databricks host URL.", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Description: "The Databricks host URL.", }, "databricks_token": schema.StringAttribute{ - Required: true, - Sensitive: true, - Description: "The Databricks access token.", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Sensitive: true, + Description: "The Databricks access token.", }, "databricks_secret_scope": schema.StringAttribute{ - Required: true, - Description: "The Databricks secret scope. Example: your-secret-scope", - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Required: true, + Description: "The Databricks secret scope. Example: your-secret-scope", }, }, } @@ -253,11 +250,26 @@ func (r *IntegrationDatabricksResource) Update(ctx context.Context, req resource return } + _, err := r.client.UpdateIntegrationAuth(infisical.UpdateIntegrationAuthRequest{ + Integration: infisical.IntegrationAuthTypeDatabricks, + IntegrationAuthId: plan.IntegrationAuthID.ValueString(), + AccessToken: plan.DatabricksAccessToken.ValueString(), + URL: plan.DatabricksHostURL.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError( + "Error updating integration auth", + err.Error(), + ) + return + } + // Update the integration updatedIntegration, err := r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ ID: state.IntegrationID.ValueString(), Environment: plan.Environment.ValueString(), SecretPath: plan.SecretPath.ValueString(), + App: plan.DatabricksSecretScope.ValueString(), }) if err != nil { diff --git a/internal/provider/resource/integration_gcp_secret_manager.go b/internal/provider/resource/integration_gcp_secret_manager.go index ae2b0b0..aa193b9 100644 --- a/internal/provider/resource/integration_gcp_secret_manager.go +++ b/internal/provider/resource/integration_gcp_secret_manager.go @@ -365,6 +365,20 @@ func (r *IntegrationGCPSecretManagerResource) Update(ctx context.Context, req re return } + _, err := r.client.UpdateIntegrationAuth(infisical.UpdateIntegrationAuthRequest{ + Integration: infisical.IntegrationAuthTypeGcpSecretManager, + IntegrationAuthId: plan.IntegrationAuthID.ValueString(), + RefreshToken: plan.ServiceAccountJson.ValueString(), + }) + + if err != nil { + resp.Diagnostics.AddError( + "Error updating integration auth", + err.Error(), + ) + return + } + var options struct { SecretPrefix types.String `tfsdk:"secret_prefix"` SecretSuffix types.String `tfsdk:"secret_suffix"` @@ -376,7 +390,7 @@ func (r *IntegrationGCPSecretManagerResource) Update(ctx context.Context, req re "secretSuffix": options.SecretSuffix.ValueString(), } - _, err := r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ + _, err = r.client.UpdateIntegration(infisical.UpdateIntegrationRequest{ IsActive: true, ID: state.IntegrationID.ValueString(), Environment: plan.Environment.ValueString(),