Skip to content
Open
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
6 changes: 2 additions & 4 deletions aws/PklProject
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ amends "pkl:Project"

dependencies {
["formae"] {
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.88.0"
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.89.0"
}

// TODO(publish): pin to the published aws schema version that includes the IAM
// ServerCertificate Resolvable patch (so `serverCert.res.arn` resolves)
["aws"] {
uri = "package://hub.platform.engineering/plugins/aws/schema/pkl/aws/aws@0.1.13"
uri = "package://hub.platform.engineering/plugins/aws/schema/pkl/aws/aws@0.1.17"
}
}
30 changes: 30 additions & 0 deletions aws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ as your original apply. **Keep this local install and its datastore:** it holds
infrastructure (VPC, database, ECS service) in state, so re-applying to upgrade depends on it.
See [Updating the agent](https://docs.formae.io/en/latest/operations/install-aws-operations/#updating-the-agent-bootstrap).

### Upgrading an installation created before the generator-drawn password

Installations bootstrapped before this version minted the database password at
evaluation time and pinned it with `setOnce`. A `setOnce` field keeps the value
it was created with, so formae refuses to route a generator through it: the
first re-apply of this version over such an installation is rejected at plan
time, naming the field. The apply changes nothing when refused. Migrate in two
applies, both with your usual flags:

1. Edit `bootstrap.pkl` to the transitional shape: add
`import "@formae/ext/random.pkl"` back to the import block, change the
secret's line to
`secretString = formae.value(random.password(24, false)).opaque`
(the old line without `.setOnce`), and remove `dbPasswordGen` from the
manifest. Apply. This re-mints the password once and releases the `setOnce`
pin; the database follows the new value in the same apply.
2. Revert to this version's `bootstrap.pkl` as shipped and apply again. The
generator draws, and the secret and database move together; from here on the
password is generator-owned.
3. Restart the deployed agent so it reads the new password — it receives
`FORMAE_DB_PASSWORD` at task start, so it keeps using the old one until its
task is replaced:
`aws ecs update-service --cluster <name>-cluster --service <name>-service --force-new-deployment`
(default `<name>` is `formae-bootstrap`).

Run the two applies back to back and restart once at the end: the deployed
agent cannot reach its database from the moment step 1 lands until the restart,
so keep that window short. Your local install, which runs these applies, is
unaffected. Fresh installations need none of this.

## Full guide

Prerequisites (ACM certificate, Tailscale setup), every flag, sizing, and day-2 operations
Expand Down
20 changes: 18 additions & 2 deletions aws/bootstrap.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import "@aws/ec2/natgateway.pkl"
import "@aws/rds/dbinstance.pkl"
import "@aws/rds/dbsubnetgroup.pkl"
import "@aws/secretsmanager/secret.pkl"
import "@formae/ext/random.pkl"

import "@aws/ecs/ecscluster.pkl"
import "@aws/ecs/taskdefinition.pkl"
Expand Down Expand Up @@ -427,6 +426,7 @@ forma {
dbSg
dbSgIngress
dbSubnetGroup
dbPasswordGen
dbSecret
db
}
Expand Down Expand Up @@ -641,11 +641,27 @@ local dbSubnetGroup = new dbsubnetgroup.DBSubnetGroup {
tags = tagged("\(n)-db-subnet-group")
}

// The database password is drawn by the agent, not minted at evaluation time:
// a generator draws once per binding (this one has no rotation block, so it
// never rotates on a schedule), and each generator draws independently, so two
// secrets can never silently share a value the way identically-parameterized
// eval-time randomness could. Bound via a differently-named local: writing
// `stack = ...` inside the generator body resolves against the generator's own
// property.
local dbStackRef: formae.StackResolvable = new { label = vars.stack.label }

local dbPasswordGen: formae.PasswordGenerator = new {
label = "\(n)-db-password-gen"
stack = dbStackRef
length = 24
symbols = false
}

local dbSecret = new secret.Secret {
label = "\(n)-db-secret"
name = "\(n)-db-password"
description = "formae database master password (generated)"
secretString = formae.value(random.password(24, false)).opaque.setOnce
secretString = dbPasswordGen.gen.value
tags = tagged("\(n)-db-secret")
}

Expand Down
2 changes: 1 addition & 1 deletion aws/vars.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dbName = "formae"
dbUser = "formae"
dbInstanceClass = "db.t4g.small"
dbEngineVersion = "16.14"
formaeImage = "ghcr.io/platform-engineering-labs/formae:0.87.0"
formaeImage = "ghcr.io/platform-engineering-labs/formae:0.89.0"
formaePort = 49684

stack: formae.Stack = new {
Expand Down