Skip to content

Upgrade to v1.76

Move a v1.75 installation to v1.76.3, on Lambda or Kubernetes. History is in the CHANGELOG.

Before you start

  • Run v1.75 on secrets layout v5 (v1.75 first if older).
  • Admit the new roles (step 1) before the first apply.
  • The first apply deletes the old issuer log group.
  • Set backup.recipients before the backup function starts (step 3).

v1.76.4 needs no action: the edge cache is opt-in and policy/compile is an unused library.

What is new

Module Lambda function Kubernetes Serves
issuer sluis-<inst>-issuer, renamed issuer Deployment sign-in, tokens, console
github sluis-<inst>-github modules.github controller passes, console, installation-token mint
slack sluis-<inst>-slack modules.slack controller passes, console
google sluis-<inst>-google modules.google directory refresh, console, sign-in identity
cloudflare sluis-<inst>-cloudflare, with a preset modules.cloudflare token and R2 mint
backup, restore sluis-<inst>-backup, -restore backup.enabled CronJobs, restore.enabled Job age-encrypted backups, format 2 (v1.76.1); the backup role lists internal/* and external/* (v1.76.3)

github-mint is gone. With <module>.remote set, the issuer opens none of that module's stores.

The issuer moves by Pulumi aliases, creating before it deletes:

Resource First apply
function, role, role policy, async invoke config replaced
log group old deleted, new created: export what you need
API integration, schedules, invoke grants updated in place
tables, parameters, keys, buckets unchanged
restore identity (RandomBytes, SSM parameter) created, with NewBackup
the v4 state secret and recovery password deleted on Layout: v5 unless Compat.KeepV4 (v1.76.0)

To keep the old name, set aws.functions.issuer to sluis or sluis-http: nothing is replaced.

1. Admit the new roles

Take the lists from the library, not from hand lists.

senders, _ := args.Senders()
roles, _ := args.Roles()
viewers, _ := args.ViewerRoles()
statements, _ := args.KeyPolicyStatements(sluispulumi.KeyPolicyOptions{Admins: adminArns})
Estate side Admit
key policies statements: SluisKeyAdminReads now covers the restore identity (v1.76.2), SluisKeyDenyRestoreIdentity refuses it to the backup role
audit senders, OTLP writers senders
viewer matchers viewers
aws.functions.github-mint, its roles in key policies, senders and viewers delete; the schema refuses the key

Set TransitionRoles for the renaming apply, and remove it once the old role is gone.

2. Update the policy

Change Action
roster controllers a service_account or aws matcher places them in all:sluis:roster-controller, else Explain refuses them
sluisctl client register it beside accessctl before workstations upgrade
Explain operators only, and audited
clients:
  sluisctl: { kind: public, requires: [all:sluis:operator], redirects: [/callback], sign_in_exchange: true }
  accessctl: { kind: public, requires: [all:sluis:operator], redirects: [/callback], sign_in_exchange: true }

sluisctl login probes for sluisctl and, on invalid_client, signs in as accessctl once. An explicit clientId: accessctl never falls back: delete it. sluisctl explain is removed.

sluisctl token --audience example --show

3. Move the backup to age keys

Make an offline break-glass key and give its recipient to the library (back up and restore on layout v5).

age-keygen -o breakglass.txt
age-keygen -y breakglass.txt     # the age1... recipient
backup, _ := sluispulumi.NewBackup(ctx, "acme", &sluispulumi.BackupArgs{
    BackupCommon:    common,
    ExtraRecipients: []string{"age1..."},
}, pulumi.Providers(awsProvider))
Change Action
ArchiveKeyAlias, KeyPolicyOptions.BackupArchive, RestoreArchive, ArchiveGrant.KeyArn delete: deprecated, they add nothing
hand-written archive statements on a shared key delete
NewRestore keep NewBackup of the same instance in the stack: it creates the identity
Kubernetes backup.config.backup set recipients; drop key, region and keys.archive, ignored with a warning for one release
an S3 or R2 store that refuses x-amz-checksum-sha256 ports.blob.s3.checksum and backup.target.checksum: md5 or none (v1.76.1)
Compat.DropV4 delete: a no-op on v5. Set Compat.KeepV4 to keep the v4 parameters

4. Apply

On Lambda, apply with the v1.76.3 library and preview again until clean. On Kubernetes, enable the modules:

modules:
  github: { enabled: true }
  slack: { enabled: true }
Setting Effect
<module>.serve in the callee's document /rpc on rpcPort (8081), TokenReview, issuers is the issuer's ServiceAccount
<module>.remote.url in the issuer's calls the Service
modules.<m>.audience token audience, the module's name
issuer pod one projected token per audience

serve with remote is refused. Another subject gets a 401.

5. Move the audit catalogue

Deploy the release's whole sluis-audit-catalogue_<version>.tar.gz with the writer.

Version Adds
1.19.0 roster.access.explained
1.20.0 refusal fields on roster.token.exchanged, roster.person.signed_in, roster.recovery.signed_in

The audit installation refuses a different document under a held version (rule).

6. Update the clients

Refusals say not authorised (request <id>) and carry X-Request-Id. Match on error, never on error_description. The reason is under request_id in the log and context.request_id in the audit trail.

Verify

Check Expect
sluis check --config issuer.yaml passes, each module too
preview clean
sluisctl login, token --show signs in as sluisctl
one console page per module answers
GitHub mint token, no github-mint call
roster.access.explained one record per look-up
roster controllers no not authorised
Kubernetes /healthz; /rpc with no bearer 200; 401
sluisctl backup list after the first run <id>.tar.gz.age with its <id>.json; sluis backup verify <id> --identity breakglass.txt passes

Roll back

  • Repoint the issuer's live alias while TransitionRoles is set.
  • Keep both clients until every workstation signs in as sluisctl.
  • A v1.76.1 backup opens only with an age identity; v1.75 cannot read it.

Decided in: ADR 0071, ADR 0072, ADR 0073.