diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c678496..7dead64 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -208,7 +208,7 @@ jobs: { printf '# PastureStack Authentication Service %s\n\n' "$RELEASE_TAG" - printf 'This release separates OIDC identity-source initialization from site-access policy updates. Access-only changes skip discovery and do not emit the provider-reload generation, unrestricted mode clears the allowlist, restricted and required policies accept only deduplicated OIDC users and groups, and access expansion requires a single-use MFA confirmation bound to the operator and canonical request digest. Initial enablement and identity-source changes retain the fresh local-recovery gate.\n\n' + printf 'This release preserves an explicit empty OIDC allowlist on the platform API wire. It avoids the generated setting client omitting an empty value, so a confirmed unrestricted transition durably clears stale restricted identities. Access-only changes continue to skip discovery and provider reload; access expansion remains protected by a single-use MFA confirmation bound to the operator and canonical request digest.\n\n' printf '## Immutable coordinates\n\n' printf -- '- Source commit: `%s`\n' "$SOURCE_SHA" printf -- '- Artifact SHA-256: `%s`\n\n' "$artifact_sha" diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index 50ce293..c34320f 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -20,7 +20,7 @@ jobs: env: DAPPER_IMAGE: pasturestack/authentication-service-dapper:${{ github.sha }} TRIVY_IMAGE: aquasec/trivy:0.73.0@sha256:7cced7cae583819fc7806d4cbc0dbbc7cad18b99f7d3e235192e6da8c091045c - VERSION_OVERRIDE: v0.4.38 + VERSION_OVERRIDE: v0.4.39 steps: - name: Check out candidate uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -70,7 +70,7 @@ jobs: } run_ci - artifact="dist/artifacts/authentication-service-0.4.38-linux-amd64.tar.xz" + artifact="dist/artifacts/authentication-service-0.4.39-linux-amd64.tar.xz" test -s "$artifact" cp "$artifact" /tmp/authentication-service-first.tar.xz rm -rf bin dist @@ -81,7 +81,7 @@ jobs: tar -xJf "$artifact" -C evidence/product test -x evidence/product/authentication-service test "$(find evidence/product -maxdepth 1 -type f | wc -l)" -eq 1 - evidence/product/authentication-service --version | grep -F '0.4.38' >/dev/null + evidence/product/authentication-service --version | grep -F '0.4.39' >/dev/null sha256sum "$artifact" > evidence/authentication-service.tar.xz.sha256 docker run --rm --entrypoint go \ --volume "$PWD:/work:ro" \ diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 6a69b9c..a428160 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -27,6 +27,9 @@ SHA-256 request digest. Reducing access does not require step-up confirmation. Unrestricted mode persists a non-null empty allowlist. Restricted and required mode entries are canonicalized and deduplicated by `externalIdType` plus `externalId`; only `oidc_user` and `oidc_group` are accepted. +The empty allowlist must be present as an explicit `value: ""` field in the +platform setting update. Generated client omission rules must not turn the +clear operation into a no-op. Operator lifecycle messages support `en-US` and `zh-TW`. Tokens, usernames, groups, identity-provider data, OpenID Connect claims, SAML documents, diff --git a/README.md b/README.md index df562c4..05e90b2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ PastureStack is an independent community effort to preserve, audit, and moderniz ## Project status -The current compatibility release is `v0.4.38`. It retains the existing Ubuntu 26.04, +The current compatibility release is `v0.4.39`. It retains the existing Ubuntu 26.04, Go 1.27.0, JWT, cookie, TLS, LDAP, GitHub, Shibboleth, dependency, and build maintenance. It adds a provider-neutral OpenID Connect authorization-code client with discovery, PKCE S256, nonce validation, @@ -21,7 +21,7 @@ single-use signed identity proof. The control platform uses that proof for an explicit account-link or reassignment decision; profile fields are never trusted as implicit account-matching keys. -Release `v0.4.38` separates OIDC identity-source changes from site-access +Release `v0.4.39` separates OIDC identity-source changes from site-access policy changes. An already-enabled provider can change access mode and its OIDC user/group allowlist without repeating discovery, emitting a provider reload generation, or repeating the five-minute local recovery ceremony. @@ -33,6 +33,10 @@ the canonical request digest. Unrestricted mode always persists an explicit empty allowlist; restricted and required modes accept and deduplicate only `oidc_user` and `oidc_group` identities. Stable error codes distinguish local recovery, MFA confirmation, and invalid access-policy failures. +The unrestricted transition sends an explicit empty allowlist value on the +platform API wire. This prevents the generated client's `omitempty` behavior +from turning a requested clear into an omitted field and retaining stale +restricted identities in the database. Product-owned imports, executable names, CLI settings, client variables, and operator messages use PastureStack naming. @@ -51,9 +55,9 @@ make build make package ``` -Set `VERSION_OVERRIDE=v0.4.38` for the reviewed identity-security compatibility +Set `VERSION_OVERRIDE=v0.4.39` for the reviewed identity-security compatibility release. Packaging produces the deterministic, versioned -`authentication-service-0.4.38-linux-amd64.tar.xz` asset. The manually +`authentication-service-0.4.39-linux-amd64.tar.xz` asset. The manually dispatched release workflow runs the full test and validation suite twice, requires byte-identical packages, verifies a fixed and attested security scanner, publishes CycloneDX SBOMs and scan evidence, and publishes the diff --git a/server/auth_server.go b/server/auth_server.go index 41f8050..9a5b29d 100644 --- a/server/auth_server.go +++ b/server/auth_server.go @@ -483,8 +483,12 @@ func updateCommonSettings(settings map[string]string) error { return err } - setting, err = PlatformClient.Setting.Update(setting, &client.Setting{ - Value: value, + // The generated Setting.Value field uses json:",omitempty". A typed + // Setting therefore drops the field when an unrestricted OIDC policy + // intentionally clears the allowlist. Use an explicit wire payload so + // an empty value remains distinguishable from "leave unchanged". + setting, err = PlatformClient.Setting.Update(setting, map[string]interface{}{ + "value": value, }) if err != nil { log.Errorf("Error updating the setting %v: %v", key, err) diff --git a/server/config_update_policy_test.go b/server/config_update_policy_test.go index 1ccc988..1815f2c 100644 --- a/server/config_update_policy_test.go +++ b/server/config_update_policy_test.go @@ -348,16 +348,21 @@ func TestPolicyOnlyUpdateClearsStoredAllowlistWithoutDiscovery(t *testing.T) { return } if request.Method == http.MethodPut { - var update struct { - Value string `json:"value"` - } + var update map[string]interface{} if err := json.NewDecoder(request.Body).Decode(&update); err != nil { t.Fatal(err) } - settings[name] = update.Value + rawValue, present := update["value"] + value, stringValue := rawValue.(string) + if !present || !stringValue { + t.Errorf("setting update omitted an explicit string value: %#v", update) + http.Error(response, "missing explicit setting value", http.StatusUnprocessableEntity) + return + } + settings[name] = value writes = append(writes, name) _, _ = fmt.Fprintf(response, `{"id":%q,"type":"setting","activeValue":%q,"value":%q,"links":{"self":%q}}`, - name, update.Value, update.Value, platformServer.URL+request.URL.Path) + name, value, value, platformServer.URL+request.URL.Path) return } http.Error(response, "unexpected platform method", http.StatusMethodNotAllowed)