Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the configured AWS partition when constructing ARNs #1223

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build
Submodule build updated 1 files
+3 −1 makelib/controlplane.mk
90 changes: 61 additions & 29 deletions config/externalname.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
//
// ID is a random UUID.
"aws_prometheus_workspace": config.IdentifierFromProvider,
"aws_prometheus_rule_group_namespace": config.TemplatedStringAsIdentifier("name", "arn:aws:aps:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:rulegroupsnamespace/{{ .parameters.workspace_id }}/{{ .external_name }}"),
"aws_prometheus_rule_group_namespace": config.TemplatedStringAsIdentifier("name", fullArnTemplate("aps", "rulegroupsnamespace/{{ .parameters.workspace_id }}/{{ .external_name }}")),
// Uses the ID of workspace, workspace_id parameter.
"aws_prometheus_alert_manager_definition": config.IdentifierFromProvider,

Expand Down Expand Up @@ -141,7 +141,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// cloudtrail
//
// Cloudtrails can be imported using the name arn:aws:cloudtrail:us-west-1:153891904029:trail/foobar
"aws_cloudtrail": config.TemplatedStringAsIdentifier("name", "arn:aws:cloudtrail:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:trail/{{ .external_name }}"),
"aws_cloudtrail": config.TemplatedStringAsIdentifier("name", fullArnTemplate("cloudtrail", "trail/{{ .external_name }}")),
// Event data stores can be imported using their arn
"aws_cloudtrail_event_data_store": config.IdentifierFromProvider,

Expand Down Expand Up @@ -419,9 +419,11 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{

// ecs
//
// unlike all other resources, ecs cluster and service overwrite their external name configs in ecs/config.go.
// TODO: centralize the external name configs to this file.
"aws_ecs_cluster": config.TemplatedStringAsIdentifier("name", "arn:aws:ecs:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:cluster/{{ .external_name }}"),
"aws_ecs_service": config.NameAsIdentifier,
"aws_ecs_capacity_provider": config.TemplatedStringAsIdentifier("name", "arn:aws:ecs:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:capacity-provider/{{ .external_name }}"),
"aws_ecs_capacity_provider": config.TemplatedStringAsIdentifier("name", fullArnTemplate("ecs", "capacity-provider/{{ .external_name }}")),
// Imported using ARN that has a random substring, revision at the end:
// arn:aws:ecs:us-east-1:012345678910:task-definition/mytaskfamily:123
"aws_ecs_task_definition": config.IdentifierFromProvider,
Expand Down Expand Up @@ -518,7 +520,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// each with their own name.
// "aws_glue_partition_index": config.IdentifierFromProvider,
// Imported using ARN: arn:aws:glue:us-west-2:123456789012:registry/example
"aws_glue_registry": config.TemplatedStringAsIdentifier("registry_name", "arn:aws:glue:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:registry/{{ .external_name }}"),
"aws_glue_registry": config.TemplatedStringAsIdentifier("registry_name", fullArnTemplate("glue", "registry/{{ .external_name }}")),
// Glue Registries can be imported using arn
// Example: arn:aws:glue:us-west-2:123456789012:schema/example/example
"aws_glue_schema": config.IdentifierFromProvider,
Expand Down Expand Up @@ -558,7 +560,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// No import
"aws_iam_group_membership": config.IdentifierFromProvider,
// IAM SAML Providers can be imported using the arn
"aws_iam_saml_provider": config.TemplatedStringAsIdentifier("name", "arn:aws:iam::{{ .setup.client_metadata.account_id }}:saml-provider/{{ .external_name }}"),
"aws_iam_saml_provider": config.TemplatedStringAsIdentifier("name", regionlessArnTemplate("iam", "saml-provider/{{ .external_name }}")),
// IAM Server Certificates can be imported using the name
"aws_iam_server_certificate": config.NameAsIdentifier,
// IAM service-linked roles can be imported using role ARN that contains the
Expand Down Expand Up @@ -899,14 +901,6 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// aws_dynamodb_tag can be imported by using the DynamoDB resource identifier and key, separated by a comma (,)
"aws_dynamodb_tag": config.TemplatedStringAsIdentifier("", "{{ .parameters.resource_arn }},{{ .parameters.key }}"),

// sns
//
// SNS Topics can be imported using the topic arn
"aws_sns_topic": config.TemplatedStringAsIdentifier("name", "arn:aws:sns:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:{{ .external_name }}"),
// SNS Topic Subscriptions can be imported using the subscription arn that
// contains a random substring in the end.
"aws_sns_topic_subscription": config.IdentifierFromProvider,

// backup
//
// Backup Framework can be imported using the id which corresponds to the name of the Backup Framework
Expand Down Expand Up @@ -955,18 +949,18 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// kinesis
//
// Even though the documentation says the ID is name, it uses ARN..
"aws_kinesis_stream": config.TemplatedStringAsIdentifier("name", "arn:aws:kinesis:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:stream/{{ .external_name }}"),
"aws_kinesis_stream": config.TemplatedStringAsIdentifier("name", fullArnTemplate("kinesis", "stream/{{ .external_name }}")),
// Kinesis Stream Consumers can be imported using the Amazon Resource Name (ARN)
// that has a random substring.
"aws_kinesis_stream_consumer": config.IdentifierFromProvider,

// kinesisanalytics
//
"aws_kinesis_analytics_application": config.TemplatedStringAsIdentifier("name", "arn:aws:kinesisanalytics:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:application/{{ .external_name }}"),
"aws_kinesis_analytics_application": config.TemplatedStringAsIdentifier("name", fullArnTemplate("kinesisanalytics", "application/{{ .external_name }}")),

// kinesisanalyticsv2
//
"aws_kinesisanalyticsv2_application": config.TemplatedStringAsIdentifier("name", "arn:aws:kinesisanalytics:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:application/{{ .external_name }}"),
"aws_kinesisanalyticsv2_application": config.TemplatedStringAsIdentifier("name", fullArnTemplate("kinesisanalytics", "application/{{ .external_name }}")),
// aws_kinesisanalyticsv2_application can be imported by using application_name together with snapshot_name
// e.g. example-application/example-snapshot
"aws_kinesisanalyticsv2_application_snapshot": FormattedIdentifierUserDefinedNameLast("snapshot_name", "/", "application_name"),
Expand Down Expand Up @@ -1012,7 +1006,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// lambda
//
// Lambda Function Aliases are identified by their ARN, like arn:aws:lambda:eu-west-1:123456789012:function:lambda-function:alias
"aws_lambda_alias": config.TemplatedStringAsIdentifier("name", "arn:aws:lambda:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:function:{{ .parameters.function_name }}:{{ .external_name }}"),
"aws_lambda_alias": config.TemplatedStringAsIdentifier("name", fullArnTemplate("lambda", "function:{{ .parameters.function_name }}:{{ .external_name }}")),
// Code Signing Configs can be imported using their ARN that has a random
// substring in the end.
// arn:aws:lambda:us-west-2:123456789012:code-signing-config:csc-0f6c334abcdea4d8b
Expand Down Expand Up @@ -1226,8 +1220,8 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{

// sfn
//
"aws_sfn_activity": config.TemplatedStringAsIdentifier("name", "arn:aws:states:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:activity/{{ .external_name }}"),
"aws_sfn_state_machine": config.TemplatedStringAsIdentifier("name", "arn:aws:states:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:stateMachine:{{ .external_name }}"),
"aws_sfn_activity": config.TemplatedStringAsIdentifier("name", fullArnTemplate("states", "activity/{{ .external_name }}")),
"aws_sfn_state_machine": config.TemplatedStringAsIdentifier("name", fullArnTemplate("states", "stateMachine:{{ .external_name }}")),

// dax
//
Expand Down Expand Up @@ -1377,7 +1371,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// CodePipelines can be imported using the name
"aws_codepipeline": config.NameAsIdentifier,
// CodePipeline Webhooks can be imported by their ARN: arn:aws:codepipeline:us-west-2:123456789012:webhook:example
"aws_codepipeline_webhook": config.TemplatedStringAsIdentifier("name", "arn:aws:codepipeline:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:webhook:{{ .external_name }}"),
"aws_codepipeline_webhook": config.TemplatedStringAsIdentifier("name", fullArnTemplate("codepipeline", "webhook:{{ .external_name }}")),
// CodeDeploy CustomActionType can be imported using the id
"aws_codepipeline_custom_action_type": config.IdentifierFromProvider,

Expand Down Expand Up @@ -1606,7 +1600,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// batch
//
// Batch Scheduling Policy can be imported using the arn: arn:aws:batch:us-east-1:123456789012:scheduling-policy/sample
"aws_batch_scheduling_policy": config.TemplatedStringAsIdentifier("name", "arn:aws:batch:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:scheduling-policy/{{ .external_name }}"),
"aws_batch_scheduling_policy": config.TemplatedStringAsIdentifier("name", fullArnTemplate("batch", "scheduling-policy/{{ .external_name }}")),
// Batch Job Definition can be imported using ARN that has a random substring, revision at the end:
// arn:aws:batch:us-east-1:123456789012:job-definition/sample:1
"aws_batch_job_definition": config.IdentifierFromProvider,
Expand Down Expand Up @@ -1684,7 +1678,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// arn:aws:securityhub:eu-west-1:312940875350:action/custom/a
// TODO: following configuration assumes the `a` in the above ARN
// is the security hub custom action identifier
"aws_securityhub_action_target": config.TemplatedStringAsIdentifier("identifier", "arn:aws:securityhub:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:action/custom/{{ .external_name }}"),
"aws_securityhub_action_target": config.TemplatedStringAsIdentifier("identifier", fullArnTemplate("securityhub", "action/custom/{{ .external_name }}")),
// imported using the arn that has a random substring:
// arn:aws:securityhub:eu-west-1:123456789098:finding-aggregator/abcd1234-abcd-1234-1234-abcdef123456
"aws_securityhub_finding_aggregator": config.IdentifierFromProvider,
Expand Down Expand Up @@ -1724,7 +1718,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
//
// config.NameAsIdentifier did not work, the identifier for the resource turned out to be an ARN
// arn:aws:cloudformation:us-west-1:123456789123:stack/networking-stack/1e691240-6f2c-11ed-8f91-06094dc221f3
"aws_cloudformation_stack": TemplatedStringAsIdentifierWithNoName("arn:aws:cloudformation:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:stack/{{ .parameters.name }}/{{ .external_name }}"),
"aws_cloudformation_stack": TemplatedStringAsIdentifierWithNoName(fullArnTemplate("cloudformation", "stack/{{ .parameters.name }}/{{ .external_name }}")),
// CloudFormation StackSets can be imported using the name
"aws_cloudformation_stack_set": config.NameAsIdentifier,
// Cloudformation Stacks Instances imported using the StackSet name, target
Expand Down Expand Up @@ -1834,19 +1828,24 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
// appflow
//
// arn:aws:appflow:us-west-2:123456789012:flow/example-flow
"aws_appflow_flow": config.TemplatedStringAsIdentifier("name", "arn:aws:appflow:{{ .setup.configuration.region }}:{{ .client_metadata.account_id }}:flow/{{ .external_name }}"),
"aws_appflow_flow": config.TemplatedStringAsIdentifier("name", fullArnTemplate("appflow", "flow/{{ .external_name }}")),

// sns
//
// SNS platform applications can be imported using the ARN:
// arn:aws:sns:us-west-2:0123456789012:app/GCM/gcm_application
"aws_sns_platform_application": config.TemplatedStringAsIdentifier("name", "arn:aws:sns:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:app/GCM/{{ .external_name }}"),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out hardcoding GCM in here only works for platform applications using Google cloud messaging. Platform applications using Apple Push Notification Service use APNS. This is simply the {{ .parameters.platform }}. I'll open a separate PR for that change.

"aws_sns_platform_application": config.TemplatedStringAsIdentifier("name", fullArnTemplate("sns", "app/GCM/{{ .external_name }}")),
// no import documentation is provided
// TODO: we will need to check if normalization is possible
"aws_sns_sms_preferences": config.IdentifierFromProvider,
// SNS Topics can be imported using the topic arn
"aws_sns_topic": config.TemplatedStringAsIdentifier("name", fullArnTemplate("sns", "{{ .external_name }}")),
// SNS Topic Policy can be imported using the topic ARN:
// arn:aws:sns:us-west-2:0123456789012:my-topic
"aws_sns_topic_policy": FormattedIdentifierFromProvider("", "arn"),
// SNS Topic Subscriptions can be imported using the subscription arn that
// contains a random substring in the end.
"aws_sns_topic_subscription": config.IdentifierFromProvider,

// servicecatalog
//
Expand Down Expand Up @@ -2133,7 +2132,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
"aws_iot_topic_rule": config.NameAsIdentifier,
// IoT topic rule destinations can be imported using the arn
// arn:aws:iot:us-west-2:123456789012:ruledestination/vpc/2ce781c8-68a6-4c52-9c62-63fe489ecc60
"aws_iot_topic_rule_destination": TemplatedStringAsProviderDefinedIdentifier("arn:aws:iot:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:ruledestination/vpc/{{ .external_name }}"),
"aws_iot_topic_rule_destination": TemplatedStringAsProviderDefinedIdentifier(fullArnTemplate("iot", "ruledestination/vpc/{{ .external_name }}")),

// sagemaker
//
Expand Down Expand Up @@ -2316,10 +2315,10 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
"aws_networkfirewall_firewall": config.IdentifierFromProvider,
// Network Firewall Policies can be imported using their ARN
// Example: arn:aws:network-firewall:us-west-1:123456789012:firewall-policy/example
"aws_networkfirewall_firewall_policy": config.TemplatedStringAsIdentifier("name", "arn:aws:network-firewall:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:firewall-policy/{{ .external_name }}"),
"aws_networkfirewall_firewall_policy": config.TemplatedStringAsIdentifier("name", fullArnTemplate("network-firewall", "firewall-policy/{{ .external_name }}")),
// Network Firewall Rule Groups can be imported using their ARN
// Example: arn:aws:network-firewall:us-west-1:123456789012:stateful-rulegroup/example
"aws_networkfirewall_rule_group": config.TemplatedStringAsIdentifier("", "arn:aws:network-firewall:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:{{ .parameters.type | ToLower }}-rulegroup/{{ .external_name }}"),
"aws_networkfirewall_rule_group": config.TemplatedStringAsIdentifier("", fullArnTemplate("network-firewall", "{{ .parameters.type | ToLower }}-rulegroup/{{ .external_name }}")),
// Network Firewall Logging Configurations can be imported using the firewall_arn
// Example: arn:aws:network-firewall:us-west-1:123456789012:firewall/example
"aws_networkfirewall_logging_configuration": config.IdentifierFromProvider,
Expand Down Expand Up @@ -2696,7 +2695,7 @@ var TerraformPluginSDKExternalNameConfigs = map[string]config.ExternalName{
"aws_evidently_project": config.IdentifierFromProvider,
// CloudWatch Evidently Segment can be imported using the arn
// Example: arn:aws:evidently:us-west-2:123456789012:segment/example
"aws_evidently_segment": config.TemplatedStringAsIdentifier("name", "arn:aws:evidently:{{ .setup.configuration.region }}:{{ .setup.client_metadata.account_id }}:segment/{{ .external_name }}"),
"aws_evidently_segment": config.TemplatedStringAsIdentifier("name", fullArnTemplate("evidently", "segment/{{ .external_name }}")),

// fis
//
Expand Down Expand Up @@ -3169,3 +3168,36 @@ func eksPodIdentityAssociation() config.ExternalName {
}
return e
}

// fullArnTemplate builds a templated string for constructing a terraform id component which is an ARN, which includes
// the aws partition, service, region, account id, and resource. This is by far the most common form of ARN.
// e.g. arn:aws:ec2:ap-south-1:123456789012:instance/i-1234567890ab
func fullArnTemplate(service string, resource string) string {
return genericArnTemplate(service, resource, false, false)

}

// regionlessArnTemplate builds a templated string for constructing a terraform id component which is an ARN of a
// resource which is regionless, but specific to your account id. It includes the partition, service, account id, and
// resource.
// e.g. arn:aws:iam::123456789012:role/example
func regionlessArnTemplate(service string, resource string) string {
return genericArnTemplate(service, resource, false, true)
}

// genericArnTemplate builds a templated string for constructing a terraform id component which is an ARN of any format.
// It always includes the aws partition, service, and resource. Unless you specify to elide them, it will also include
// templates which resolve to the region (from the spec.forProvider) and the account id (calculated from the provider
// config).
func genericArnTemplate(service string, resource string, elideAccountId bool, elideRegion bool) string {
region := "{{ .setup.configuration.region }}"
if elideRegion {
region = ""
}
accountId := "{{ .setup.client_metadata.account_id }}"
if elideAccountId {
accountId = ""
}
partition := "{{ .setup.configuration.partition }}"
return fmt.Sprintf("arn:%s:%s:%s:%s:%s", partition, service, region, accountId, resource)
}
29 changes: 28 additions & 1 deletion internal/clients/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,23 @@ import (

const (
keyAccountID = "account_id"
keyPartition = "partition"
keyRegion = "region"
)

type SetupConfig struct {
TerraformProvider *schema.Provider
}

// iamRegions holds the region used for signing IAM credentials for each AWS partition.
var iamRegions = map[string]string{
"aws": "us-east-1",
"aws-gov": "us-gov-west-1",
"aws-cn": "cn-northeast-1",
"aws-iso": "us-iso-east-1",
"aws-iosb": "us-isob-east-1",
}

func SelectTerraformSetup(config *SetupConfig) terraform.SetupFn { // nolint:gocyclo
return func(ctx context.Context, c client.Client, mg resource.Managed) (terraform.Setup, error) {
pc := &v1beta1.ProviderConfig{}
Expand Down Expand Up @@ -64,7 +74,13 @@ func SelectTerraformSetup(config *SetupConfig) terraform.SetupFn { // nolint:goc
}
ps.ClientMetadata = map[string]string{
keyAccountID: account,
keyPartition: "aws",
}

if pc.Spec.Endpoint != nil && pc.Spec.Endpoint.PartitionID != nil {
ps.ClientMetadata[keyPartition] = *pc.Spec.Endpoint.PartitionID
}

// several external name configs depend on the setup.Configuration for templating region
ps.Configuration = map[string]any{
keyRegion: awsCfg.Region,
Expand Down Expand Up @@ -98,11 +114,22 @@ func getAWSConfigWithDefaultRegion(ctx context.Context, c client.Client, obj run
return nil, err
}
if cfg.Region == "" && obj.GetObjectKind().GroupVersionKind().Group == "iam.aws.upbound.io" {
cfg.Region = "us-east-1"
cfg.Region = getIAMRegion(pc)
}
return cfg, nil
}

func getIAMRegion(pc *v1beta1.ProviderConfig) string {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should write a unit test for this function.

defaultRegion := "us-east-1"
if pc == nil || pc.Spec.Endpoint == nil || pc.Spec.Endpoint.PartitionID == nil {
return defaultRegion
}
if region, ok := iamRegions[*pc.Spec.Endpoint.PartitionID]; ok {
return region
}
return defaultRegion
}

type metaOnlyPrimary struct {
meta any
}
Expand Down
Loading