Skip to content

Commit 9d84adc

Browse files
authored
feat: The s2s auth policies are all now scope to individual keys/buckets (#943)
BREAKING CHANGE: If upgrading from a previous release, you will see the s2s auth policies being destroyed and recreated. Please be aware that the new policies will be finer scoped to individual KMS key or COS buckets so if you had any other infrastructure that relied on the policies created by this solution, you should check to see if they are impacted before upgrading
1 parent 72f135a commit 9d84adc

File tree

11 files changed

+331
-42
lines changed

11 files changed

+331
-42
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,7 @@ module "cluster_pattern" {
874874
| [ibm_container_vpc_cluster.cluster](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/container_vpc_cluster) | resource |
875875
| [ibm_container_vpc_worker_pool.pool](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/container_vpc_worker_pool) | resource |
876876
| [ibm_cos_bucket.buckets](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/cos_bucket) | resource |
877+
| [ibm_iam_authorization_policy.cos_bucket_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource |
877878
| [ibm_iam_authorization_policy.policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource |
878879
| [ibm_is_placement_group.placement_group](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_placement_group) | resource |
879880
| [ibm_is_security_group.security_group](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_security_group) | resource |
@@ -895,6 +896,7 @@ module "cluster_pattern" {
895896
| [random_string.random_cos_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
896897
| [time_sleep.wait_30_seconds](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
897898
| [time_sleep.wait_for_authorization_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
899+
| [time_sleep.wait_for_authorization_policy_buckets](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
898900
| [time_sleep.wait_for_vpc_creation_data](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
899901
| [ibm_container_addons.existing_addons](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/container_addons) | data source |
900902
| [ibm_container_cluster_versions.cluster_versions](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/container_cluster_versions) | data source |

atracker.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ resource "ibm_atracker_target" "atracker_target" {
3131
target_type = "cloud_object_storage"
3232

3333
# Wait for buckets and auth policies to ensure successful provision
34-
depends_on = [ibm_cos_bucket.buckets, ibm_iam_authorization_policy.policy]
34+
depends_on = [ibm_cos_bucket.buckets, ibm_iam_authorization_policy.policy, ibm_iam_authorization_policy.cos_bucket_policy]
3535
}
3636

3737
resource "ibm_atracker_route" "atracker_route" {

dynamic_values.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module "dynamic_values" {
88
prefix = var.prefix
99
key_management = var.key_management
1010
key_management_guid = module.key_management.key_management_guid
11+
key_management_key_map = module.key_management.key_map
1112
clusters = var.clusters
1213
vpcs = var.vpcs
1314
resource_groups = local.resource_groups

dynamic_values/config_modules/service_authorizations/service_authorizations.tf

Lines changed: 157 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ variable "key_management_guid" {
1010
description = "Key management guid"
1111
}
1212

13+
variable "key_management_key_map" {
14+
description = "Key management key IDs"
15+
}
16+
1317
variable "cos_instance_ids" {
1418
description = "Map of COS instance IDs"
1519
}
@@ -38,28 +42,123 @@ variable "clusters" {
3842
description = "Add cluster to kms auth policies"
3943
}
4044

45+
variable "vsi" {
46+
description = "Add vsi block storage to auth policies"
47+
}
48+
49+
variable "vpcs" {
50+
description = "Direct reference to vpcs variable"
51+
}
52+
53+
##############################################################################
54+
55+
##############################################################################
56+
# BUCKET MAP
57+
# Create a flattened out map of all configured cos buckets.
58+
# This map will have the bucket name as the key, and have attributes
59+
# from both the bucket and the parent instance that we need in further queries.
60+
# This map will be used to perform lookups based on bucket name to get data related to either
61+
# the bucket itself or its parent instance.
4162
##############################################################################
63+
module "cos_bucket_map" {
64+
source = "../list_to_map"
65+
list = flatten([
66+
for instance in var.cos :
67+
[
68+
for bucket in instance.buckets :
69+
[
70+
{
71+
name = bucket.name
72+
instance_name = instance.name
73+
bucket_key_name = lookup(bucket, "kms_key", null)
74+
skip_kms_s2s_auth_policy = lookup(instance, "skip_kms_s2s_auth_policy", false)
75+
skip_flowlogs_s2s_auth_policy = lookup(instance, "skip_flowlogs_s2s_auth_policy", false)
76+
skip_atracker_s2s_auth_policy = lookup(instance, "skip_atracker_s2s_auth_policy", false)
77+
}
78+
]
79+
]
80+
])
81+
}
4282

4383
##############################################################################
4484
# Locals
4585
##############################################################################
4686

4787
locals {
4888
target_key_management_service = lookup(var.key_management, "name", null) != null ? lookup(var.key_management, "use_hs_crypto", false) == true ? "hs-crypto" : "kms" : null
89+
90+
# create a list of keys used for all buckets, since we are going to scope the auth policy to keys.
91+
# doing this in a local first becase it needs a distinct to get rid of duplicates from same keys used
92+
# on multiple buckets, and a distinct on the final map may error in terraform for_each before first apply.
93+
cos_bucket_key_list_distinct = distinct(
94+
flatten([
95+
for bucket in module.cos_bucket_map.value :
96+
[
97+
{
98+
instance_name = bucket.instance_name
99+
bucket_key_name = lookup(bucket, "bucket_key_name", null)
100+
}
101+
] if !bucket.skip_kms_s2s_auth_policy
102+
])
103+
)
104+
105+
# get all keys that will be used for VSI block storage
106+
# this is combination of all boot volume keys, plus the extra storage volume keys
107+
block_storage_key_list_distinct = distinct(
108+
flatten([
109+
[
110+
for vsi in var.vsi :
111+
[
112+
{ block_key_name = lookup(vsi, "boot_volume_encryption_key_name", null) }
113+
]
114+
],
115+
[
116+
for vsi in var.vsi :
117+
[
118+
for block in coalesce(lookup(vsi, "block_storage_volumes", null), []) :
119+
[
120+
{ block_key_name = lookup(block, "encryption_key", null) }
121+
]
122+
]
123+
]
124+
])
125+
)
126+
127+
# get all keys used in clusters
128+
# is combination of boot keys and config keys
129+
kube_key_list_distinct = distinct(
130+
flatten([
131+
[
132+
for cluster in var.clusters :
133+
[
134+
{ cluster_key_name = lookup(cluster, "boot_volume_crk_name", null) }
135+
]
136+
],
137+
[
138+
for cluster in var.clusters :
139+
[
140+
{ cluster_key_name = lookup(cluster.kms_config, "crk_name", null) }
141+
] if lookup(cluster, "kms_config", null) != null
142+
]
143+
])
144+
)
49145
}
50146

51147
module "kms_to_block_storage" {
52148
source = "../list_to_map"
53149
list = [
54-
for instance in(var.skip_kms_block_storage_s2s_auth_policy ? [] : ["block-storage"]) :
150+
for instance in local.block_storage_key_list_distinct :
55151
{
56-
name = instance
152+
name = "block-storage-to-${instance.block_key_name}"
57153
source_service_name = "server-protect"
58-
description = "Allow block storage volumes to be encrypted by KMS instance"
154+
description = "Allow block storage volumes to be encrypted by KMS key"
59155
roles = ["Reader"]
60156
target_service_name = local.target_key_management_service
61157
target_resource_instance_id = var.key_management_guid
62-
} if local.target_key_management_service != null
158+
target_resource_type = "key"
159+
target_resource_id = split(":", var.key_management_key_map[instance.block_key_name].crn)[9]
160+
target_resource_account_id = trimprefix(split(":", var.key_management_key_map[instance.block_key_name].crn)[6], "a/")
161+
} if local.target_key_management_service != null && !var.skip_kms_block_storage_s2s_auth_policy && instance.block_key_name != null
63162
]
64163
}
65164

@@ -69,15 +168,18 @@ module "kms_to_block_storage" {
69168
module "kube_to_kms" {
70169
source = "../list_to_map"
71170
list = [
72-
for instance in(length(var.clusters) > 0 ? ["containers-kubernetes"] : []) :
171+
for instance in local.kube_key_list_distinct :
73172
{
74-
name = instance
173+
name = "kube-to-${instance.cluster_key_name}"
75174
source_service_name = "containers-kubernetes"
76175
description = "Allow cluster to be encrypted by KMS instance"
77176
roles = ["Reader"]
78177
target_service_name = local.target_key_management_service
79178
target_resource_instance_id = var.key_management_guid
80-
} if local.target_key_management_service != null && !var.skip_kms_kube_s2s_auth_policy
179+
target_resource_type = "key"
180+
target_resource_id = split(":", var.key_management_key_map[instance.cluster_key_name].crn)[9]
181+
target_resource_account_id = trimprefix(split(":", var.key_management_key_map[instance.cluster_key_name].crn)[6], "a/")
182+
} if local.target_key_management_service != null && !var.skip_kms_kube_s2s_auth_policy && instance.cluster_key_name != null
81183
]
82184
}
83185

@@ -90,32 +192,58 @@ module "kube_to_kms" {
90192
module "cos_to_key_management" {
91193
source = "../list_to_map"
92194
list = [
93-
for instance in var.cos :
195+
for bucket_key in local.cos_bucket_key_list_distinct :
94196
{
95-
name = "cos-${instance.name}-to-key-management"
197+
name = "cos-${bucket_key.instance_name}-to-key-${bucket_key.bucket_key_name}"
96198
source_service_name = "cloud-object-storage"
97-
source_resource_instance_id = split(":", var.cos_instance_ids[instance.name])[7]
98-
description = "Allow COS instance to read from KMS instance"
199+
source_resource_instance_id = split(":", var.cos_instance_ids[bucket_key.instance_name])[7]
200+
description = "Allow COS instance to read KMS key"
99201
roles = ["Reader"]
100202
target_service_name = local.target_key_management_service
101203
target_resource_instance_id = var.key_management_guid
102-
} if local.target_key_management_service != null && !instance.skip_kms_s2s_auth_policy
204+
target_resource_type = "key"
205+
target_resource_id = split(":", var.key_management_key_map[bucket_key.bucket_key_name].crn)[9]
206+
target_resource_account_id = trimprefix(split(":", var.key_management_key_map[bucket_key.bucket_key_name].crn)[6], "a/")
207+
} if local.target_key_management_service != null && bucket_key.bucket_key_name != null
103208
]
104209
}
105210

211+
##############################################################################
212+
# VPC Flow Logs to COS bucket
213+
##############################################################################
214+
locals {
215+
flow_log_bucket_list_distinct = distinct(
216+
flatten([
217+
for vpc in var.vpcs :
218+
[
219+
{
220+
bucket_name = vpc.flow_logs_bucket_name
221+
}
222+
] if lookup(vpc, "flow_logs_bucket_name", null) != null
223+
])
224+
)
225+
}
226+
227+
# NOTE:
228+
# Due to terraform plan cycle issues, we cannot reference the true bucket instance here in this module,
229+
# so we will pass back the reference name of the bucket in `target_resource_id` and look up details
230+
# when applying the auth policy
106231
module "flow_logs_to_cos" {
107232
source = "../list_to_map"
108233
list = [
109-
for instance in var.cos :
234+
for instance in local.flow_log_bucket_list_distinct :
110235
{
111-
name = "flow-logs-${instance.name}"
236+
name = "flow-logs-${instance.bucket_name}"
112237
source_service_name = "is"
113238
source_resource_type = "flow-log-collector"
114239
description = "Allow flow logs write access cloud object storage instance"
115240
roles = ["Writer"]
116241
target_service_name = "cloud-object-storage"
117-
target_resource_instance_id = split(":", var.cos_instance_ids[instance.name])[7]
118-
} if !instance.skip_flowlogs_s2s_auth_policy
242+
target_resource_instance_id = null
243+
target_resource_type = "bucket"
244+
target_resource_id = instance.bucket_name
245+
target_resource_account_id = null
246+
} if !module.cos_bucket_map.value[instance.bucket_name].skip_flowlogs_s2s_auth_policy
119247
]
120248
}
121249

@@ -125,30 +253,29 @@ module "flow_logs_to_cos" {
125253
# Atracker to COS
126254
##############################################################################
127255

128-
locals {
129-
atracker_cos_instance = var.atracker_cos_bucket == null ? null : one(flatten([
130-
for instance in var.cos :
131-
[
132-
for bucket in instance.buckets :
133-
[instance.name] if bucket.name == var.atracker_cos_bucket
134-
] if !instance.skip_atracker_s2s_auth_policy
135-
]))
136-
}
137-
256+
# NOTE:
257+
# Due to terraform plan cycle issues, we cannot reference the true bucket instance here in this module,
258+
# so we will pass back the reference name of the bucket in `target_resource_id` and look up details
259+
# when applying the auth policy
138260
module "atracker_to_cos" {
139261
source = "../list_to_map"
140262
list = [
141-
for instance in(var.atracker_cos_bucket != null && local.atracker_cos_instance != null ? ["atracker-to-cos"] : []) :
263+
for instance in ["atracker-to-cos"] :
142264
{
143265
name = instance
144266
source_service_name = "atracker"
145-
description = "Allow atracker to write to COS"
267+
description = "Allow atracker to write to COS bucket"
146268
roles = ["Object Writer"]
147269
target_service_name = "cloud-object-storage"
148-
target_resource_instance_id = split(":", var.cos_instance_ids[local.atracker_cos_instance])[7]
149-
}
270+
target_resource_instance_id = null
271+
target_resource_type = "bucket"
272+
target_resource_id = var.atracker_cos_bucket
273+
target_resource_account_id = null
274+
} if var.atracker_cos_bucket != null && !try(module.cos_bucket_map.value[var.atracker_cos_bucket].skip_atracker_s2s_auth_policy, false)
150275
]
151276
}
277+
# DEV NOTE: needed a `try()` on the `cos_bucket_map` lookup above to take care of the case where atracker is turned off
278+
# and a bucket name is NULL. This causes plan errors which the try should catch.
152279

153280
##############################################################################
154281
# Outputs

dynamic_values/service_authorizations.tf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ module "service_authorizations" {
66
source = "./config_modules/service_authorizations"
77
key_management = var.key_management
88
key_management_guid = var.key_management_guid
9+
key_management_key_map = var.key_management_key_map
910
cos = var.cos
1011
cos_instance_ids = local.cos_instance_ids
1112
skip_kms_block_storage_s2s_auth_policy = var.skip_kms_block_storage_s2s_auth_policy
1213
skip_all_s2s_auth_policies = var.skip_all_s2s_auth_policies
1314
skip_kms_kube_s2s_auth_policy = var.skip_kms_kube_s2s_auth_policy
1415
atracker_cos_bucket = var.atracker_cos_bucket
1516
clusters = var.clusters
17+
vsi = var.vsi
18+
vpcs = var.vpcs
1619
}
1720

1821
##############################################################################

dynamic_values/variables.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ variable "key_management_guid" {
108108
description = "Key Management GUID"
109109
}
110110

111+
variable "key_management_key_map" {
112+
description = "Key management key IDs"
113+
}
114+
111115
##############################################################################
112116

113117
##############################################################################

outputs.tf

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,20 @@ output "security_group_data" {
348348

349349
output "service_authorization_names" {
350350
description = "List of service authorization names"
351-
value = keys(ibm_iam_authorization_policy.policy)
351+
value = concat(keys(ibm_iam_authorization_policy.policy), keys(ibm_iam_authorization_policy.cos_bucket_policy))
352352
}
353353

354354
output "service_authorization_data" {
355355
description = "List of service authorization data"
356356
value = flatten([
357-
for policy in ibm_iam_authorization_policy.policy :
358-
policy
357+
[
358+
for policy in ibm_iam_authorization_policy.policy :
359+
policy
360+
],
361+
[
362+
for bucket_policy in ibm_iam_authorization_policy.cos_bucket_policy :
363+
bucket_policy
364+
]
359365
])
360366
}
361367

patterns/dynamic_values/cloud_object_storage.tf

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ module "cloud_object_storage" {
1414
use_existing_cos_for_vpc_flowlogs = var.use_existing_cos_for_vpc_flowlogs
1515
endpoint_type = var.existing_cos_endpoint_type
1616
create_atracker_storage = var.add_atracker_route
17-
# skip the kms s2s auth policy for existing cos instances: if existing kms name is null or empty
18-
skip_kms_auth_for_existing_cos = coalesce(var.existing_kms_instance_name, "~EMPTY~") != "~EMPTY~" ? true : false
1917
}
2018

2119
##############################################################################

patterns/vsi-extension/main.tf

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ locals {
3939
for subnet in data.ibm_is_vpc.vpc_by_id.subnets :
4040
subnet if can(regex(local.default_subnet_name, subnet.name))
4141
]
42+
43+
existing_kms_instance_guid = var.boot_volume_encryption_key == null ? null : split(":", var.boot_volume_encryption_key)[7]
4244
}
4345

4446
module "vsi" {
@@ -52,10 +54,11 @@ module "vsi" {
5254
tags = var.resource_tags
5355
access_tags = var.access_tags
5456
kms_encryption_enabled = true
55-
skip_iam_authorization_policy = true
57+
skip_iam_authorization_policy = var.skip_iam_authorization_policy
5658
user_data = var.user_data
5759
image_id = data.ibm_is_image.image.id
5860
boot_volume_encryption_key = var.boot_volume_encryption_key
61+
existing_kms_instance_guid = local.existing_kms_instance_guid
5962
security_group_ids = var.security_group_ids
6063
ssh_key_ids = [local.ssh_key_id]
6164
machine_type = var.vsi_instance_profile

0 commit comments

Comments
 (0)