@@ -110,13 +110,54 @@ resource "aws_lambda_function" "user_bot" {
110110 filename = data. archive_file . user_bot_placeholder . output_path
111111 source_code_hash = data. archive_file . user_bot_placeholder . output_base64sha256
112112
113+ // The name, not the token. The function reads and decrypts the value at cold start.
114+ environment {
115+ variables = {
116+ SLACK_TOKEN_PARAMETER = aws_ssm_parameter.user_bot_slack_token.name
117+ }
118+ }
119+
113120 depends_on = [aws_cloudwatch_log_group . user_bot ]
114121
115122 lifecycle {
116123 ignore_changes = [filename , source_code_hash ]
117124 }
118125}
119126
127+ // ---------------------------------------------------------------------------
128+ // Slack bot token
129+ // ---------------------------------------------------------------------------
130+
131+ // The Slack app's bot token (xoxb-...). Terraform creates the parameter with a
132+ // placeholder; the real token is set by hand with `aws ssm put-parameter --overwrite`
133+ // (see lambda/user-bot/README.md), so it never appears in git or in Terraform state.
134+ // See hackforla/devops-security#212.
135+ //
136+ // value_wo rather than value is what keeps the token out of state. With `value`, the
137+ // provider reads the decrypted parameter back into state on every refresh. With the
138+ // write-only value_wo, its read sets `value` to null, and the value is only ever
139+ // written when value_wo_version changes.
140+ //
141+ // Two traps, both because the real value lives outside Terraform:
142+ // - Never change value_wo_version. That writes the placeholder over the real token,
143+ // and the bot refuses to send until the token is set again.
144+ // - Do not change other arguments, such as description, in place. The provider then
145+ // sends an empty value and the apply fails.
146+ resource "aws_ssm_parameter" "user_bot_slack_token" {
147+ region = local. user_bot_region
148+
149+ name = " /user-bot/slack-bot-token"
150+ description = " Slack bot token for the user-bot Lambda. Set by hand; see lambda/user-bot/README.md in hackforla/devops-security."
151+ type = " SecureString"
152+
153+ value_wo = " placeholder-set-by-hand"
154+ value_wo_version = 1
155+
156+ lifecycle {
157+ prevent_destroy = true
158+ }
159+ }
160+
120161// ---------------------------------------------------------------------------
121162// Execution role
122163// ---------------------------------------------------------------------------
@@ -165,6 +206,14 @@ resource "aws_iam_role_policy" "user_bot" {
165206 }
166207 }
167208 },
209+ // No kms:Decrypt statement: the parameter uses the AWS-managed aws/ssm key, whose
210+ // key policy already allows decryption through SSM for principals in the account.
211+ {
212+ Sid = " ReadSlackToken"
213+ Effect = " Allow"
214+ Action = " ssm:GetParameter"
215+ Resource = aws_ssm_parameter.user_bot_slack_token.arn
216+ },
168217 {
169218 Sid = " WriteOwnLogs"
170219 Effect = " Allow"
0 commit comments