carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1# region-remote-host: what an off-cloud CARLOS host (a Raspberry Pi today,
2# tito's hq box later) needs from AWS — a region data bucket plus a scoped
3# IAM principal, since there is no EC2 instance profile to lean on off-cloud.
4#
5# Residency is enforced the same way as region-host: the principal reaches
6# ITS OWN region's data bucket and nothing else. A wrong-region host fails
7# loudly at the IAM layer, same property as every cloud host. The deployment
8# bucket is grandfathered-optional (var.deployment_bucket_arn, default "")
9# — a fleet-API box needs no platform-bucket grant at all; see the
10# aws_iam_user_policy.host_deployment_bucket comment below.
11#
12# The data bucket follows the deployment-buckets convention for this class
13# of bucket (infra/modules/README.md, infra/modules/deployment-buckets/
14# main.tf): boxes are disposable, this bucket is not, hence versioning,
15# AES256 SSE, a full public-access block, and prevent_destroy.
16
17terraform {
18 required_version = ">= 1.10"
19 required_providers {
20 aws = {
21 source = "hashicorp/aws"
22 version = ">= 6.0"
23 }
24 }
25}
26
27resource "aws_s3_bucket" "data" {
28 bucket = var.data_bucket_name
29 lifecycle { prevent_destroy = true }
30}
31
32resource "aws_s3_bucket_versioning" "data" {
33 bucket = aws_s3_bucket.data.id
34 versioning_configuration { status = "Enabled" }
35}
36
37resource "aws_s3_bucket_public_access_block" "data" {
38 bucket = aws_s3_bucket.data.id
39 block_public_acls = true
40 block_public_policy = true
41 ignore_public_acls = true
42 restrict_public_buckets = true
43}
44
45resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
46 bucket = aws_s3_bucket.data.id
47 rule {
48 apply_server_side_encryption_by_default { sse_algorithm = "AES256" }
49 }
50}
51
52# The off-cloud host principal. No instance profile exists off EC2; this
53# user + access key is the C6 decision-6 shape (IAM Roles Anywhere is the
54# later hardening — the policy below is what either principal would carry).
55resource "aws_iam_user" "host" {
56 name = "carlos-remote-host-${var.region_code}"
57}
58
59resource "aws_iam_access_key" "host" {
60 user = aws_iam_user.host.name
61}
62
63# Scope: THIS region's data bucket, always — wrong-region-fails-at-IAM, same
64# property as every cloud host.
65#
66# The action list is not independently chosen: region-host's
67# aws_iam_role_policy.host_buckets (infra/modules/region-host/main.tf) is
68# the authority for what this principal shape may do to S3, and this
69# policy copies it verbatim — object actions (GetObject/PutObject/
70# DeleteObject) scoped to the object ARNs, list/location actions
71# (ListBucket/GetBucketLocation) scoped to the bucket ARNs.
72resource "aws_iam_user_policy" "host" {
73 name = "carlos-remote-host-${var.region_code}"
74 user = aws_iam_user.host.name
75 policy = jsonencode({
76 Version = "2012-10-17"
77 # Sibling staging (instance-move spec, issue #331) — same push-only
78 # moves/* shape as region-host's host_buckets policy; keep the two in
79 # step (this module's README already carries that rule for the S3
80 # action list).
81 Statement = concat([
82 {
83 Sid = "OwnRegionDataObjects"
84 Effect = "Allow"
85 Action = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"]
86 Resource = ["${aws_s3_bucket.data.arn}/*"]
87 },
88 {
89 Sid = "OwnRegionDataList"
90 Effect = "Allow"
91 Action = ["s3:ListBucket", "s3:GetBucketLocation"]
92 Resource = [aws_s3_bucket.data.arn]
93 },
94 # Host lifecycle claim, Plan 2 (spec C3, §6.2, §6.5): exact-version
95 # list, read and delete on this box's OWN data bucket. Sibling-region
96 # moves/* grants are unchanged: the destination region retires its
97 # own staging (spec N13).
98 {
99 Sid = "PullDoorObjectVersions"
100 Effect = "Allow"
101 Action = ["s3:GetObjectVersion"]
102 Resource = ["${aws_s3_bucket.data.arn}/*"]
103 },
104 {
105 Effect = "Allow"
106 Action = ["s3:DeleteObjectVersion"]
107 Resource = ["${aws_s3_bucket.data.arn}/*"]
108 },
109 {
110 Sid = "PullDoorVersionList"
111 Effect = "Allow"
112 Action = ["s3:ListBucketVersions"]
113 Resource = [aws_s3_bucket.data.arn]
114 },
115 ],
116 [for arn in var.sibling_staging_arns : {
117 Effect = "Allow"
118 Action = ["s3:PutObject", "s3:DeleteObject"]
119 Resource = "${arn}/moves/*"
120 }],
121 [for arn in var.sibling_staging_arns : {
122 Effect = "Allow"
123 Action = ["s3:ListBucket"]
124 Resource = arn
125 Condition = { StringLike = { "s3:prefix" = "moves/*" } }
126 }]
127 )
128 })
129}
130
131# Deployment-bucket grant: GRANDFATHERED, direct-S3 boxes only. A fleet-API
132# box (issue #73) never needs this — it reaches the platform's deployment
133# bucket through the console's edge, over the same tunnel token it dials
134# with, so var.deployment_bucket_arn is left "" and this whole statement
135# (and the IAM policy resource carrying it) is omitted. count, not a dynamic
136# block, matches the same optional-statement pattern used by
137# aws_iam_role_policy.host_route53 in the region-host module.
138resource "aws_iam_user_policy" "host_deployment_bucket" {
139 count = var.deployment_bucket_arn != "" ? 1 : 0
140 name = "carlos-remote-host-${var.region_code}-deployment-bucket"
141 user = aws_iam_user.host.name
142 policy = jsonencode({
143 Version = "2012-10-17"
144 Statement = [
145 {
146 Sid = "DeploymentBucketObjects"
147 Effect = "Allow"
148 Action = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"]
149 Resource = ["${var.deployment_bucket_arn}/*"]
150 },
151 {
152 Sid = "DeploymentBucketList"
153 Effect = "Allow"
154 Action = ["s3:ListBucket", "s3:GetBucketLocation"]
155 Resource = [var.deployment_bucket_arn]
156 },
157 ]
158 })
159}
160