main.tf
252 lines 7.1 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 ######################################################################
2 # Paperclip orchestrator — AWS infrastructure
3 #
4 # Provisions ONE EC2 t3.large with:
5 # - Ubuntu 24.04 LTS (latest Canonical AMI for the chosen region)
6 # - 30 GB gp3 EBS root volume (encrypted)
7 # - IAM role with read/write access to /knowtation/paperclip/* in SSM (push-secrets on instance)
8 # - Security group: SSH from your home IP, Tailscale UDP, HTTP/HTTPS
9 # - User-data script that joins Tailscale on first boot and runs install.sh
10 #
11 # After 'terraform apply':
12 # - SSH via Tailscale: ssh ubuntu@paperclip-prod
13 # - SSM secrets pre-seeded with empty placeholders for hub URL + vault ID
14 # - Real secrets pushed later via deploy/paperclip/scripts/push-secrets.sh
15 ######################################################################
16
17 # Latest Ubuntu 24.04 LTS AMI for the chosen region.
18 data "aws_ami" "ubuntu" {
19 most_recent = true
20 owners = ["099720109477"] # Canonical
21
22 filter {
23 name = "name"
24 values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
25 }
26
27 filter {
28 name = "virtualization-type"
29 values = ["hvm"]
30 }
31
32 filter {
33 name = "architecture"
34 values = ["x86_64"]
35 }
36 }
37
38 # Default VPC + first default subnet — keeps blast radius narrow for a single-instance deploy.
39 data "aws_vpc" "default" {
40 default = true
41 }
42
43 data "aws_subnets" "default" {
44 filter {
45 name = "vpc-id"
46 values = [data.aws_vpc.default.id]
47 }
48 }
49
50 # Random suffix so multiple deploys (prod + staging) don't collide on names.
51 resource "random_id" "suffix" {
52 byte_length = 4
53 }
54
55 #################
56 # SSH key pair
57 #################
58
59 resource "aws_key_pair" "operator" {
60 key_name = "knowtation-paperclip-${var.environment}-${random_id.suffix.hex}"
61 public_key = var.ssh_public_key
62 }
63
64 #################
65 # Security group
66 #################
67
68 resource "aws_security_group" "paperclip" {
69 name = "knowtation-paperclip-${var.environment}-${random_id.suffix.hex}"
70 description = "Paperclip orchestrator: SSH from home IP only, Tailscale, HTTP for Lets Encrypt"
71 vpc_id = data.aws_vpc.default.id
72
73 # SSH (TCP 22) — your home IP only. Primary access is Tailscale; this is fallback.
74 ingress {
75 description = "SSH from operator home IP"
76 from_port = 22
77 to_port = 22
78 protocol = "tcp"
79 cidr_blocks = [var.home_ip_cidr]
80 }
81
82 # Tailscale (UDP 41641) — outbound NAT-traversal; ingress is for direct connections.
83 ingress {
84 description = "Tailscale direct connection"
85 from_port = 41641
86 to_port = 41641
87 protocol = "udp"
88 cidr_blocks = ["0.0.0.0/0"]
89 }
90
91 # HTTP — Let's Encrypt HTTP-01 challenge ONLY. Paperclip dashboard is Tailscale-only.
92 ingress {
93 description = "Lets Encrypt HTTP-01 challenge port 80"
94 from_port = 80
95 to_port = 80
96 protocol = "tcp"
97 cidr_blocks = ["0.0.0.0/0"]
98 }
99
100 # HTTPS — only if you intentionally expose the Paperclip dashboard publicly. Closed by default.
101 # Uncomment to expose. Recommended: keep closed, use Tailscale Funnel for sharing.
102 # ingress {
103 # description = "HTTPS public dashboard (off by default)"
104 # from_port = 443
105 # to_port = 443
106 # protocol = "tcp"
107 # cidr_blocks = ["0.0.0.0/0"]
108 # }
109
110 egress {
111 description = "All outbound (Paperclip calls DeepInfra, HeyGen, ElevenLabs, Descript, Knowtation Hub)"
112 from_port = 0
113 to_port = 0
114 protocol = "-1"
115 cidr_blocks = ["0.0.0.0/0"]
116 }
117 }
118
119 ##################################
120 # IAM role + SSM Parameter access
121 ##################################
122
123 resource "aws_iam_role" "paperclip" {
124 name = "knowtation-paperclip-${var.environment}-${random_id.suffix.hex}"
125
126 assume_role_policy = jsonencode({
127 Version = "2012-10-17"
128 Statement = [{
129 Effect = "Allow"
130 Principal = { Service = "ec2.amazonaws.com" }
131 Action = "sts:AssumeRole"
132 }]
133 })
134 }
135
136 resource "aws_iam_role_policy" "ssm_read" {
137 name = "ssm-paperclip-secrets-readwrite"
138 role = aws_iam_role.paperclip.id
139
140 policy = jsonencode({
141 Version = "2012-10-17"
142 Statement = [
143 {
144 Effect = "Allow"
145 Action = [
146 "ssm:GetParameter",
147 "ssm:GetParameters",
148 "ssm:GetParametersByPath",
149 "ssm:PutParameter",
150 "ssm:DeleteParameter"
151 ]
152 Resource = "arn:aws:ssm:${var.aws_region}:*:parameter/knowtation/paperclip/*"
153 },
154 {
155 Effect = "Allow"
156 Action = [
157 "kms:Decrypt",
158 "kms:Encrypt",
159 "kms:GenerateDataKey"
160 ]
161 Resource = "*"
162 Condition = {
163 StringEquals = {
164 "kms:ViaService" = "ssm.${var.aws_region}.amazonaws.com"
165 }
166 }
167 }
168 ]
169 })
170 }
171
172 resource "aws_iam_role_policy_attachment" "ssm_managed_core" {
173 role = aws_iam_role.paperclip.name
174 policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
175 }
176
177 resource "aws_iam_instance_profile" "paperclip" {
178 name = "knowtation-paperclip-${var.environment}-${random_id.suffix.hex}"
179 role = aws_iam_role.paperclip.name
180 }
181
182 ##############################################
183 # SSM placeholders for non-secret config items
184 ##############################################
185
186 # Empty placeholders — push-secrets.sh fills the real ones interactively.
187 # These exist so the install script's first boot doesn't fail on missing keys.
188
189 resource "aws_ssm_parameter" "hub_url" {
190 count = var.knowtation_hub_url == "" ? 0 : 1
191 name = "/knowtation/paperclip/KNOWTATION_HUB_URL"
192 description = "Knowtation hosted Hub URL"
193 type = "String"
194 value = var.knowtation_hub_url
195 tier = "Standard"
196 }
197
198 resource "aws_ssm_parameter" "vault_id" {
199 name = "/knowtation/paperclip/KNOWTATION_VAULT_ID"
200 description = "Knowtation vault ID for the Hub MCP"
201 type = "String"
202 value = var.knowtation_vault_id
203 tier = "Standard"
204 }
205
206 #################
207 # EC2 instance
208 #################
209
210 resource "aws_instance" "paperclip" {
211 ami = data.aws_ami.ubuntu.id
212 instance_type = var.instance_type
213 subnet_id = data.aws_subnets.default.ids[0]
214
215 vpc_security_group_ids = [aws_security_group.paperclip.id]
216 key_name = aws_key_pair.operator.key_name
217 iam_instance_profile = aws_iam_instance_profile.paperclip.name
218
219 metadata_options {
220 http_tokens = "required" # IMDSv2 only
221 http_put_response_hop_limit = 2
222 }
223
224 root_block_device {
225 volume_type = "gp3"
226 volume_size = var.ebs_size_gb
227 encrypted = true
228 delete_on_termination = true
229 tags = {
230 Name = "knowtation-paperclip-${var.environment}-root"
231 }
232 }
233
234 user_data = templatefile("${path.module}/user-data.sh.tpl", {
235 tailscale_auth_key = var.tailscale_auth_key
236 hostname = "paperclip-${var.environment}"
237 })
238
239 user_data_replace_on_change = false
240
241 tags = {
242 Name = "knowtation-paperclip-${var.environment}"
243 }
244
245 lifecycle {
246 ignore_changes = [
247 ami, # don't replace box just because Canonical published a new minor AMI
248 user_data, # user_data only matters first boot
249 root_block_device[0].tags,
250 ]
251 }
252 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago