Release: v0.2.1 - #173
Merged
Merged
Conversation
Docs-only plan: bring the cutover runbook and the operator docs it leans on into line with the environments that exist as of 2026-09-10 (cfp-live-cluster, GitOps release pins, filesystem private storage, the published-branch refresh pipeline, the Slack SSO plan). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
The runbook still described a DNS flip with a lowered TTL, a staging host that never existed, an S3 bucket, a data-repo `main` branch, and a T+90/T+180 claim window + password purge. None of that matches what is deployed: - Production runs in cfp-live-cluster (ns codeforphilly-ng) at next.codeforphilly.org, alongside laddr in code-for-philly. The apex and *.codeforphilly.org already resolve to that cluster's gateway, so T-0 is one commit moving the apex/www listeners and HTTPRoute hostnames from _gateways/code-for-philly.yaml to _gateways/codeforphilly-ng.yaml and flipping CFP_SITE_HOST. Rollback is a revert of that commit. - The data repo has no `main`; the refresh is import -> merge legacy-import into published (thousands of deleted-by-us conflicts for pruned spam, resolved by taking the import) -> prune -> push, which hot-reloads sandbox and prod. First run: 36,254 -> 22,625. - Slack SAML can only be tested in the real workspace, so the plan is SSO-optional first, Test configuration against the next.* metadata, then update only the SSO URL at cutover. A bad SAML flip is fixed in Slack, not by moving the hostname back. - Legacy password sign-in persists indefinitely per account-migration.md; the T+90 mailout and T+180 purge sections are gone, and the announcement templates no longer tell members their password is going away. - Monitoring: nothing external exists yet; the T+1h window says so instead of pointing at UptimeRobot. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
…itOps Image publishing is no longer manual and production is not a future `cfp-prod-cluster`: container-publish.yml pushes :vX.Y.Z on every release tag, and each cluster repo (cfp-sandbox-cluster, cfp-live-cluster) pins the app by .holo/sources/codeforphilly-ng.toml plus images[].newTag in codeforphilly-ng/app/kustomization.yaml, applied through the releases/k8s-manifests -> deploys/k8s-manifests PR. The hand-built :sandbox image is kept only as an emergency escape hatch. Private storage is a filesystem PVC in both environments; the S3 sections are marked supported-but-unused rather than "production". Secrets are the three SealedSecrets in cfp-live-cluster/codeforphilly-ng.secrets/ (codeforphilly-secrets, codeforphilly-saml carried over from laddr's saml2, the deploy key), edited with kubeseal --merge-into. Namespaces, the hot-reload secret name, the pod-clone volume type and the monitoring status note are corrected to match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
The export command pointed at a `laddr` database and a `users` table with Handle/PasswordHash columns that don't exist. The live database is `emergence-site` (the `codeforphilly` schema on the same server is a stale 2024 copy), table `people`, columns Username/Email/Password, and the export runs inside the laddr pod with the Habitat-packaged mysql client against the service's client.cnf. The production load is kubectl cp onto the pod's private-storage PVC followed by a rollout restart, same as sandbox; the GCS/S3 upload section is reduced to a note for the unused s3 backend. Records the first prod load (2026-09-10, 21,761 profiles/credentials) and warns that repeating it resets rehashed credentials now that sign-ins are live at next.codeforphilly.org. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
docs(ops): bring the cutover runbook in line with the live cluster
Author
Changelog- docs(ops): bring the cutover runbook in line with the live cluster [#172] @themightychris
- fix(api): SAML AuthnStatement + Redirect-binding SSO endpoint [#174] @themightychris |
The SAML assertion spec described Subject, Conditions and the attribute set but never an AuthnStatement, and the implementation followed it. The Web Browser SSO profile requires at least one, and Slack's SP config sends a RequestedAuthnContext, so an assertion without one is the most likely rejection when we test against the real workspace. Spec the statement: AuthnInstant = assertion issue time, a fresh SessionIndex, and a fixed AuthnContextClassRef of ...:ac:classes:Password. That class matches what the legacy Emergence SAML2 connector emitted (setAuthnContext(AC_PASSWORD)) against this same workspace, which is worth more than the marginally more precise PasswordProtectedTransport. Slack's live "Test configuration" also failed with a 404: it sends the AuthnRequest over HTTP-Redirect (GET with a DEFLATEd SAMLRequest in the query), and although our metadata advertises that binding at the /sso Location, the spec only defined POST. Add GET to the endpoints table and describe both bindings as one flow that differs only in transport. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
Two gaps found while preparing to test the IdP against the live Slack workspace, both in the SP-initiated path. The assertion carried Subject, Conditions and AttributeStatement but no AuthnStatement, which the Web Browser SSO profile requires and which Slack's RequestedAuthnContext gives it a reason to check. Add one to the login response template between Conditions and AttributeStatement, with AuthnInstant = the assertion issue time, a fresh SessionIndex from the same id source as the Response/Assertion IDs, and the fixed class urn:oasis:names:tc:SAML:2.0:ac:classes:Password. That class is what the legacy Emergence SAML2 connector asserted against this workspace (setAuthnContext(AC_PASSWORD)), so existing Slack accounts see the same context they were established under. The placeholders ride the existing customTagReplacement path; samlify signs after that callback returns, so the statement lands inside the signed subtree. Slack's "Test configuration" then 404ed: Slack sends the AuthnRequest over HTTP-Redirect (GET with a DEFLATEd SAMLRequest in the query), and while the metadata advertised that binding at /sso, only POST was registered. Add GET /api/saml/slack/sso. Rather than call samlify's 'redirect' parser and teach the resume cookie about bindings, inflate at the edge back to the plain-base64 form the POST binding carries and run both through one handleSpInitiatedSso; samlify's redirect flow is that same inflate followed by the same parser, so nothing is lost and the cookie's samlRequest claim keeps a single shape. Fold spaces back to '+' in the query value, since a sender that leaves base64 '+' unescaped has it URL-decoded to a space. Tests cover the AuthnStatement (position, ClassRef, AuthnInstant <= IssueInstant, SessionIndex distinct from the assertion ID), verify the signature cryptographically against the metadata endpoint's cert and show that tampering with the ClassRef breaks it, and exercise the Redirect binding for signed-in, anonymous (through /sso/resume), bad ACS and non-DEFLATEd payloads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jmpurQpBHXD9yLFEGUriR
fix(api): SAML AuthnStatement + Redirect-binding SSO endpoint
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slack SSO fixes found during the first live configuration test: the IdP now accepts Slack's HTTP-Redirect binding and asserts an AuthnStatement.
Improvements
Technical