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
176 changes: 175 additions & 1 deletion .github/workflows/bridge-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ concurrency:
jobs:
bridge-required-gates:
name: Bridge component acceptance
needs: [addon, bff, dependencies, lockfiles, rust-dependencies, secrets, security, web]
needs: [addon, bff, dependencies, idp, lockfiles, rust-dependencies, secrets, security, web]
if: always()
runs-on: ubuntu-22.04
env:
Expand Down Expand Up @@ -222,6 +222,180 @@ jobs:
docker rm --force "$WEB_CONTAINER_ID"
fi

idp:
name: Dex IdP runtime qualification
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
contents: read
env:
PYTHONDONTWRITEBYTECODE: '1'
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Initialize native Dex qualification evidence
run: |
set -euo pipefail
export IDP_EVIDENCE_DIR="$RUNNER_TEMP/bridge-idp-evidence"
printf 'IDP_EVIDENCE_DIR=%s\n' "$IDP_EVIDENCE_DIR" >> "$GITHUB_ENV"
mkdir -p "$IDP_EVIDENCE_DIR"
printf 'commit=%s\nrun_id=%s\nrun_attempt=%s\n' \
"$GITHUB_SHA" "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT" \
> "$IDP_EVIDENCE_DIR/provenance.txt"
test "$(uname -m)" = x86_64
docker info --format '{{.OSType}}/{{.Architecture}}' \
| tee "$IDP_EVIDENCE_DIR/docker-platform.txt"
df -h "$RUNNER_TEMP" | tee "$IDP_EVIDENCE_DIR/disk-before.txt"
- name: Check Dex source contracts and actual reviewed module locks
id: idp_source
run: |
set -euo pipefail
KARS_DEX_PATCH_NETWORK=1 python3 -m unittest discover \
-s bridge/idp/tests -p 'test_*.py' \
2>&1 | tee "$IDP_EVIDENCE_DIR/source-contracts.log"
PYTHONPATH=bridge/idp/tests python3 -c \
'from contracts import check_modules; check_modules("bridge/idp"); print("Reviewed Go module locks verified")' \
2>&1 | tee "$IDP_EVIDENCE_DIR/module-locks.log"
PYTHONPATH=ci/tests python3 -m unittest bridge_contracts_test.ContractAggregateTests \
2>&1 | tee "$IDP_EVIDENCE_DIR/component-aggregate-contracts.log"
git diff --exit-code -- bridge/idp/locks
- name: Build and run upstream Dex root and API race suites
id: idp_upstream
run: |
set -euo pipefail
docker build --platform linux/amd64 --progress=plain \
--target upstream-tests --tag kars-bridge-idp-upstream-tests:latest \
--file bridge/idp/Dockerfile bridge/idp \
2>&1 | tee "$IDP_EVIDENCE_DIR/upstream-build.log"
- name: Collect actual upstream JSON reports and disclose skips
id: idp_reports
run: |
set -euo pipefail
container=$(docker create kars-bridge-idp-upstream-tests:latest)
trap 'docker rm "$container"' EXIT
docker cp "$container:/out/doc/upstream-tests.json" "$IDP_EVIDENCE_DIR/upstream-tests.json"
docker cp "$container:/out/doc/api-tests.json" "$IDP_EVIDENCE_DIR/api-tests.json"
docker image inspect kars-bridge-idp-upstream-tests:latest \
> "$IDP_EVIDENCE_DIR/upstream-image.json"
python3 - <<'PY'
import collections
import json
import os
from pathlib import Path

evidence = Path(os.environ["IDP_EVIDENCE_DIR"])
summary = {}
for label, filename in (("root", "upstream-tests.json"), ("api", "api-tests.json")):
tests = collections.Counter()
packages = collections.Counter()
started, finished = set(), set()
skips = []
for number, line in enumerate((evidence / filename).read_text().splitlines(), 1):
event = json.loads(line)
if not isinstance(event, dict) or not isinstance(event.get("Action"), str):
raise SystemExit(f"Invalid Go event in {filename}:{number}")
action = event["Action"]
if action in ("fail", "build-fail"):
raise SystemExit(f"Upstream failure recorded in {filename}:{number}")
package = event.get("Package")
if package and not event.get("Test"):
if action == "start":
started.add(package)
elif action in ("pass", "skip"):
finished.add(package)
packages[action] += 1
if event.get("Test") and action in ("pass", "skip"):
tests[action] += 1
if action == "skip":
skips.append({"package": package, "test": event.get("Test")})
if not started or started != finished:
raise SystemExit(f"Incomplete package results in {filename}")
if label == "root" and not tests["pass"]:
raise SystemExit("The root report contains no passing upstream tests")
summary[label] = {
"testActions": dict(tests), "packageActions": dict(packages), "skipped": skips,
}
summary["coverageLimit"] = (
"Skipped service integrations and unconfigured LDAP/cloud connectors are not "
"runtime-qualified. Memory, SQLite and static OIDC require the separate runtime harness."
)
(evidence / "upstream-summary.json").write_text(json.dumps(summary, indent=2) + "\n")
with Path(os.environ["GITHUB_STEP_SUMMARY"]).open("a") as output:
output.write("### Dex upstream reports (not final runtime qualification)\n")
for label in ("root", "api"):
counts = summary[label]
output.write(f"- {label}: test actions {counts['testActions']}; "
f"package actions {counts['packageActions']}. See retained JSON for every skip.\n")
output.write(summary["coverageLimit"] + "\n")
PY
- name: Build Dex tools and run signing-key continuity race regressions
id: idp_tools
run: |
set -euo pipefail
docker build --platform linux/amd64 --progress=plain \
--target test-tools --tag kars-bridge-idp-tools:latest \
--file bridge/idp/Dockerfile bridge/idp \
2>&1 | tee "$IDP_EVIDENCE_DIR/probe-build.log"
docker image inspect kars-bridge-idp-tools:latest > "$IDP_EVIDENCE_DIR/tools-image.json"
- name: Build the actual Azure Linux distroless Dex runtime without publishing
id: idp_runtime
run: |
set -euo pipefail
docker build --platform linux/amd64 --progress=plain \
--target runtime --tag kars-bridge-idp-qualification:latest \
--file bridge/idp/Dockerfile bridge/idp \
2>&1 | tee "$IDP_EVIDENCE_DIR/runtime-build.log"
- name: Install pinned Trivy and scan the actual Dex runtime
id: idp_scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
version: v0.70.0
cache: 'false'
scan-type: image
image-ref: kars-bridge-idp-qualification:latest
scanners: vuln
severity: HIGH,CRITICAL
ignore-unfixed: false
list-all-pkgs: 'true'
exit-code: '1'
format: json
output: ${{ runner.temp }}/bridge-idp-evidence/initial-image-scan.json
- name: Require real memory SQLite OIDC linkage inventory and fresh-cache scans
id: idp_qualification
if: ${{ !cancelled() && steps.idp_runtime.outcome == 'success' }}
run: |
set -euo pipefail
{
trivy_path="$(command -v trivy)"
test -x "$trivy_path"
printf '%s\n' "$trivy_path" > "$IDP_EVIDENCE_DIR/trivy-cli-path.txt"
"$trivy_path" --version | tee "$IDP_EVIDENCE_DIR/trivy-cli-version.txt"
grep -Eq '^Version: 0[.]70[.]0([[:space:]]|$)' "$IDP_EVIDENCE_DIR/trivy-cli-version.txt"
python3 bridge/idp/tests/qualify.py \
--image kars-bridge-idp-qualification:latest \
--tools-image kars-bridge-idp-tools:latest \
--trivy "$trivy_path" --evidence "$IDP_EVIDENCE_DIR/runtime"
} 2>&1 | tee "$IDP_EVIDENCE_DIR/runtime-qualification.log"
- name: Record Dex step outcomes without overriding failures
if: always()
env:
IDP_STEP_RESULTS: ${{ toJSON(steps) }}
run: |
set -euo pipefail
mkdir -p "$IDP_EVIDENCE_DIR"
printf '%s\n' "$IDP_STEP_RESULTS" > "$IDP_EVIDENCE_DIR/step-outcomes.json"
- name: Retain actual Dex qualification evidence including failures
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4
with:
name: bridge-idp-qualification-${{ github.sha }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/bridge-idp-evidence/
if-no-files-found: error

addon:
name: Add-on install and uninstall
runs-on: ubuntu-latest
Expand Down
14 changes: 14 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ The separately built aggregator downloads the official, checksum-verified
remain with redistributed images; other bundled components retain their
respective licenses.

## Optional curated Dex identity provider

`bridge/idp` builds Dex v2.45.1 from upstream commit
`11d2eeb52b42e1980e14cb91e69dd9e3faab2076` under Apache License 2.0.
It preserves upstream attribution and explicitly discloses Kars packaging,
dependency updates, two literal-format fixes and a test-only historical
certificate clock adjustment. It is not an unmodified upstream Dex image.

The original upstream license, source/module snapshots, reviewed patches and
linked dependency notices accompany the built image. `bridge/idp/NOTICE`
describes their installed locations. Generated module/checksum files and
patch inputs retain their exact verified bytes; Kars headers must not rewrite
them or imply ownership of upstream source. New Kars wrapper code is MIT.

## Vendored TypeScript SDK build

`vendor/agt/microsoft-agent-governance-sdk-4.0.0-agt-bdea1097.tgz`
Expand Down
15 changes: 15 additions & 0 deletions bridge/docs/governed-credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,21 @@ are never written to this evidence. Collection cannot qualify any assertion.
TLS negatives, 9447/9448 paths, CNI peer denial, and credential rotation remain
required unchanged.

`routerRollout` separately captures an unready router's bounded container state,
restart count, previous exit reason/code/signal, fixed startup-log markers and
classified kubelet probe events. It checks the target namespace/Deployment and
ReplicaSet/Pod UID chain, then rechecks all snapshot resource versions and Pod
process state. Old observer versions are reported as comparison booleans, not
accepted as current capability authority. This does not relax the existing
readiness collector or the network experiment's ready-process requirement.
Current and previous process log tails remain separate; failed log/event reads
are explicit without discarding otherwise stable process-state evidence.
Events must reference the exact Pod UID and `spec.containers{inference-router}`;
their coverage is the Pod lifetime, not proof of the current process's failure.
Unknown text, probe URLs/bodies, container IDs and raw errors are not retained.
A logged listener-start intention is not a successful bind or reachability
proof. The original failed result and three dependent blocked cases remain.

An operator template-drift or writer-restoration refusal also records `enrollmentTemplateDrift`.
It compares the fixture's pre-preview runtime, controller and BFF Deployment
snapshots with the existing CLI review and current objects, using the shipped
Expand Down
9 changes: 9 additions & 0 deletions bridge/docs/public-beta-helm.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ its label is not a rebuild. Do not overwrite normal release repositories with
beta images or assume historical chart image defaults are publicly available.
See [Bridge image builds and deployment](deployment.md#install).

If enabling bundled Dex, separately build and qualify the
[curated IdP runtime](../idp/README.md), then set `idp.dex.image` to its approved
immutable registry reference. The historical upstream chart default is not
an approved image merely because it is a default: the September 16 scan of
upstream v2.45.1 found High/Critical issues and blocked its use. Do not deploy
it, suppress those findings, or use the dev-role preview as authentication.
The curated source recipe is not itself a published or runtime-qualified image.
External OIDC remains an alternative when genuinely configured by the operator.

Before changing an existing installation, inventory Helm ownership, CRD/schema
compatibility, workload placement and external credentials. Back up sensitive
configuration privately with restricted file permissions, outside the checkout.
Expand Down
18 changes: 18 additions & 0 deletions bridge/idp/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

*
!Dockerfile
!Dockerfile.locks
!locks
!locks/**
!patches
!patches/**
!scripts
!scripts/**
!tests
!tests/probe.go
!tests/probe_test.go
!NOTICE
!LICENSE
!README.md
68 changes: 68 additions & 0 deletions bridge/idp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# Context: bridge/idp. Native builds only: build each architecture on its own
# hosted worker. Bookworm's glibc baseline is older than Azure Linux 3's.
FROM golang:1.26.8-bookworm@sha256:9fdc884aacc3bec89b20ffc69f4bb369c78210e3e4f600387b5128b12c199f81 AS source
ENV GOTOOLCHAIN=local GOWORK=off GOPROXY=https://proxy.golang.org \
GOSUMDB=sum.golang.org GOMODCACHE=/work/mod GOCACHE=/work/cache \
GOFLAGS=-mod=readonly CGO_ENABLED=1
WORKDIR /src/dex
COPY locks/inputs.lock /packaging/locks/inputs.lock
COPY scripts/fetch-source.sh scripts/source-inventory.sh scripts/apply-source-patches.sh /packaging/scripts/
COPY patches/ /packaging/patches/
RUN sh /packaging/scripts/fetch-source.sh

FROM source AS dependencies
# Deliberately absent until a hosted Go resolver's output has been reviewed.
# A runtime build MUST fail rather than resolve new versions implicitly.
COPY locks/generated/ /locks/
COPY locks/requests.txt /packaging/locks/requests.txt
COPY scripts/verify-locks.sh /packaging/scripts/verify-locks.sh
RUN sh /packaging/scripts/verify-locks.sh \
&& go mod download && go mod verify \
&& cmp go.mod /locks/go.mod && cmp go.sum /locks/go.sum \
&& cd api/v2 && go mod download && go mod verify \
&& cmp go.mod /locks/api/v2/go.mod && cmp go.sum /locks/api/v2/go.sum

FROM dependencies AS build
COPY scripts/build.sh /packaging/scripts/build.sh
COPY scripts/notices.go scripts/notices_test.go /packaging/scripts/
COPY NOTICE LICENSE README.md /packaging/
RUN go test -count=1 /packaging/scripts/notices.go /packaging/scripts/notices_test.go \
&& sh /packaging/scripts/build.sh

FROM build AS compatibility-tests
RUN go test -race -count=1 -v \
-run '^(TestKarsAuthorizationErrorDescriptionsLiteral|TestVerifyUnsignedMessageAndSignedAssertionWithRootXmlNs|TestKarsSAMLFixtureCertificateValidity)$' \
./server ./connector/saml

# Kept separate from shipping layers. Full upstream package suites; external
# service integration tests still require the upstream documented services.
FROM compatibility-tests AS upstream-tests
COPY scripts/upstream-tests.sh /packaging/scripts/upstream-tests.sh
RUN sh /packaging/scripts/upstream-tests.sh

FROM build AS test-tools
COPY tests/probe.go tests/probe_test.go /packaging/tests/
# Only the external HTTP probe is pure Go. Dex itself always uses real CGO.
RUN go test -race -count=1 -v /packaging/tests/probe.go /packaging/tests/probe_test.go \
&& CGO_ENABLED=0 go build -trimpath -buildvcs=false -o /out/probe /packaging/tests/probe.go
ENTRYPOINT ["/out/probe"]

FROM mcr.microsoft.com/azurelinux/distroless/base:3.0@sha256:4377af4aa7a810b7d59f691eae5066895a71aa3eee4cfb4eba527bbebff16479 AS runtime
LABEL org.opencontainers.image.title="Kars Dex security rebuild" \
org.opencontainers.image.description="Dex v2.45.1 with disclosed Kars source, dependency and packaging patches; not an unmodified upstream image" \
org.opencontainers.image.version="v2.45.1-kars.1" \
org.opencontainers.image.source="https://github.com/Azure/kars" \
org.opencontainers.image.licenses="Apache-2.0 AND MIT" \
io.kars.dex.upstream.revision="11d2eeb52b42e1980e14cb91e69dd9e3faab2076"
COPY --from=build /out/dex /usr/local/bin/dex
COPY --from=build /src/dex/web /srv/dex/web
COPY --from=build /out/doc/ /usr/share/doc/dex/
COPY --from=build --chown=1001:1001 /out/etc/ /etc/dex/
COPY --from=build --chown=1001:1001 /out/data/ /var/dex/
USER 1001:1001
EXPOSE 5556 5557 5558
ENTRYPOINT ["/usr/local/bin/dex"]
CMD ["serve", "/etc/dex/config.yaml"]
33 changes: 33 additions & 0 deletions bridge/idp/Dockerfile.locks
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# Separate from the image recipe: even legacy builders must never resolve
# dependency versions as an incidental step in a normal runtime image build.
FROM golang:1.26.8-bookworm@sha256:9fdc884aacc3bec89b20ffc69f4bb369c78210e3e4f600387b5128b12c199f81 AS source
ENV GOTOOLCHAIN=local GOWORK=off GOPROXY=https://proxy.golang.org \
GOSUMDB=sum.golang.org GOMODCACHE=/work/mod GOCACHE=/work/cache \
GOFLAGS=-mod=readonly CGO_ENABLED=1
WORKDIR /src/dex
COPY locks/inputs.lock /packaging/locks/inputs.lock
COPY scripts/fetch-source.sh scripts/source-inventory.sh scripts/apply-source-patches.sh /packaging/scripts/
COPY patches/ /packaging/patches/
RUN sh /packaging/scripts/fetch-source.sh

FROM source AS lock-generation
COPY locks/requests.txt /packaging/locks/requests.txt
COPY scripts/generate-locks.sh /packaging/scripts/generate-locks.sh
RUN sh /packaging/scripts/generate-locks.sh

FROM scratch AS lock-artifact
COPY --from=lock-generation --chown=1001:1001 /out/ /
USER 1001:1001

FROM source AS lock-replay
COPY --from=lock-generation --chown=1001:1001 /out/ /out/
COPY --chown=1001:1001 locks/generated/ /reviewed/
USER 1001:1001
RUN test "$(id -u)" = 1001 && test "$(id -g)" = 1001 \
&& cd /reviewed && sha256sum --check --strict SHA256SUMS \
&& cmp SHA256SUMS /out/SHA256SUMS \
&& cd /out && sha256sum --check --strict /reviewed/SHA256SUMS \
&& printf 'KARS_DEX_LOCK_REPLAY_PASSED\n'
21 changes: 21 additions & 0 deletions bridge/idp/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) Microsoft Corporation.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading