diff --git a/aws/PklProject b/aws/PklProject index 2ab08f1..b2e6708 100644 --- a/aws/PklProject +++ b/aws/PklProject @@ -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" } } diff --git a/aws/README.md b/aws/README.md index ed3ccc6..887754a 100644 --- a/aws/README.md +++ b/aws/README.md @@ -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 -cluster --service -service --force-new-deployment` + (default `` 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 diff --git a/aws/bootstrap.pkl b/aws/bootstrap.pkl index 5af6b4e..56b0055 100644 --- a/aws/bootstrap.pkl +++ b/aws/bootstrap.pkl @@ -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" @@ -427,6 +426,7 @@ forma { dbSg dbSgIngress dbSubnetGroup + dbPasswordGen dbSecret db } @@ -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") } diff --git a/aws/vars.pkl b/aws/vars.pkl index d53c9e4..a323dd1 100644 --- a/aws/vars.pkl +++ b/aws/vars.pkl @@ -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 {