Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.40
VERSION_OVERRIDE: v0.4.41
steps:
- name: Check out candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
}

run_ci
artifact="dist/artifacts/authentication-service-0.4.40-linux-amd64.tar.xz"
artifact="dist/artifacts/authentication-service-0.4.41-linux-amd64.tar.xz"
test -s "$artifact"
cp "$artifact" /tmp/authentication-service-first.tar.xz
rm -rf bin dist
Expand All @@ -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.40' >/dev/null
evidence/product/authentication-service --version | grep -F '0.4.41' >/dev/null
sha256sum "$artifact" > evidence/authentication-service.tar.xz.sha256
docker run --rm --entrypoint go \
--volume "$PWD:/work:ro" \
Expand Down
3 changes: 3 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ enabled, unchanged OIDC provider must not repeat discovery, key retrieval, or
provider initialization. Initial enablement, changing provider type, or
changing the OIDC identity source still requires a fresh local-recovery check
and successful provider initialization.
This includes reload requests emitted by platform setting events after the
policy write: an already-live provider adopts the updated access policy in
memory, while startup and source changes still initialize the provider.

Expanding access requires a one-time Engine MFA security confirmation bound to
the authenticated operator, purpose `oidcAccessPolicyUpdate`, and the canonical
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ PastureStack is an independent community effort to preserve, audit, and moderniz

## Project status

The current compatibility release is `v0.4.40`. It retains the existing Ubuntu 26.04,
The current compatibility release is `v0.4.41`. 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,
Expand All @@ -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.40` separates OIDC identity-source changes from site-access
Release `v0.4.41` 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.
Expand All @@ -43,6 +43,11 @@ empty legacy OIDC keys over the authoritative access mode or allowlist. This
keeps restricted `oidc_user` and `oidc_group` entries intact across service and
Server container restarts while retaining the one-time migration path for old
installations.
Platform setting events can request a reload after a policy save. When the
active OIDC provider and identity source are unchanged, that reload now adopts
the persisted access policy in memory without repeating discovery, key
retrieval, or provider construction. Startup, first enablement, provider
switches, and identity-source changes retain the full initialization path.

Product-owned imports, executable names, CLI settings, client variables, and
operator messages use PastureStack naming.
Expand All @@ -61,9 +66,9 @@ make build
make package
```

Set `VERSION_OVERRIDE=v0.4.40` for the reviewed identity-security compatibility
Set `VERSION_OVERRIDE=v0.4.41` for the reviewed identity-security compatibility
release. Packaging produces the deterministic, versioned
`authentication-service-0.4.40-linux-amd64.tar.xz` asset. The manually
`authentication-service-0.4.41-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
Expand Down
9 changes: 9 additions & 0 deletions server/auth_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,15 @@ func Reload(fromUpdate bool) (bool, error) {
return false, nil
}

if strings.EqualFold(authConfig.Provider, oidcProviderName) &&
canApplyOIDCReloadWithoutInitialization(
authConfigInMemory, authConfig, provider != nil) {
log.Info("Applying OpenID Connect access-policy reload without provider initialization")
authConfigInMemory = authConfig
<-*refreshReqChannel
return false, nil
}

if err := prepareProviderConfig(&authConfig); err != nil {
<-*refreshReqChannel
return false, err
Expand Down
13 changes: 13 additions & 0 deletions server/config_update_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,19 @@ func planOIDCConfigUpdate(current model.AuthConfig, requested model.AuthConfig)
}, nil
}

// canApplyOIDCReloadWithoutInitialization keeps platform setting events from
// turning an access-policy-only save into a second provider initialization.
// The provider must already be live; startup, first enablement, provider
// switches, and identity-source changes continue through the full reload path.
func canApplyOIDCReloadWithoutInitialization(current model.AuthConfig,
requested model.AuthConfig, providerReady bool) bool {
if !providerReady {
return false
}
plan, err := planOIDCConfigUpdate(current, requested)
return err == nil && plan.SameProvider && !plan.RequiresProviderInitialization
}

func oidcIdentitySourceChanged(current model.OIDCConfig, requested model.OIDCConfig) bool {
return current.WellKnownURL != requested.WellKnownURL ||
current.ClientID != requested.ClientID ||
Expand Down
27 changes: 27 additions & 0 deletions server/config_update_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,33 @@ func TestOIDCPolicyOnlyUpdateSkipsRecoveryAndProviderInitialization(t *testing.T
}
}

func TestOIDCPolicyOnlyReloadSkipsProviderInitialization(t *testing.T) {
current := oidcConfigForPolicyTest(true, "restricted",
oidcIdentity("oidc_user", "alice"))
policyOnly := current
policyOnly.AllowedIdentities = append(policyOnly.AllowedIdentities,
oidcIdentity("oidc_group", "operators"))

if !canApplyOIDCReloadWithoutInitialization(current, policyOnly, true) {
t.Fatal("a live unchanged OIDC provider would be initialized for a policy-only reload")
}
if canApplyOIDCReloadWithoutInitialization(current, policyOnly, false) {
t.Fatal("startup skipped required OIDC provider initialization")
}

sourceChange := policyOnly
sourceChange.OIDCConfig.ClientID = "replacement-client"
if canApplyOIDCReloadWithoutInitialization(current, sourceChange, true) {
t.Fatal("an OIDC identity-source change skipped provider initialization")
}

initialEnable := current
initialEnable.Enabled = false
if canApplyOIDCReloadWithoutInitialization(initialEnable, current, true) {
t.Fatal("initial OIDC enablement skipped provider initialization")
}
}

func TestExpiredLocalRecoveryOnlyBlocksIdentitySourceChanges(t *testing.T) {
now := time.UnixMilli(1_800_000_000_000)
expiredRecovery := map[string]string{
Expand Down
Loading