Skip to content

Release: v0.2.1 - #173

Merged
themightychris merged 10 commits into
mainfrom
develop
Sep 10, 2026
Merged

Release: v0.2.1#173
themightychris merged 10 commits into
mainfrom
develop

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

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

themightychris and others added 6 commits September 10, 2026 02:06
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
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown
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

themightychris and others added 4 commits September 10, 2026 02:36
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
@themightychris
themightychris merged commit 07bc8d0 into main Sep 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant