Shibboleth IdP containerized (with bundled plugins)
4.4K
A hardened, container image of the Shibboleth Identity Provider maintained by CSC (IT Center for Science, Finland) for Finnish research and education federations
This is a base image: it ships a working IdP with a broad set of plugins, and you extend it with your own organization-specific configuration and credentials.
Every component below is verified against a vendor SHA256 hash recorded in the Dockerfile, making the build essentially deterministic:
| Component | Version |
|---|---|
| Shibboleth IdP | 5.2.3 |
| Jetty | 12.1.13 |
| OpenJDK (headless) | 21 (LTS) |
| Alpine base | 3.24.2 (pinned) |
| MariaDB JDBC client | 3.5.10 |
| PostgreSQL JDBC driver | 42.7.13 |
Bundled plugins:
| Plugin | Version |
|---|---|
oidc-common | 3.3.1 |
oidc-config | 3.0.1 |
oidc-op | 4.3.2 |
oidc-rp | 2.3.0 |
| WebAuthn | 1.4.2 |
| JDBC storage | 2.1.0 |
| GEANT user-profile | 1.2.1 |
| OAuth2 device grant | 0.9.3 |
| Step-up authn | 2.0.0 |
| Reverse-proxy authn | 2.0.0 |
| Authn discovery | 2.3.0 |
| Nashorn scripting | 2.0.0 |
| CandourID | 1.0.0 |
| csc-library | 0.14.0 |
Versions above are a snapshot; the Dockerfile is the source of truth (Renovate bumps the version ARGs there).
The image is built to run unprivileged on podman, Kubernetes and OpenShift:
root:0 with group permissions mirroring the owner; the image declares USER 1000 and also works under OpenShift's randomly-assigned UID (always in group 0). There is no setuid module — the JVM runs directly as the unprivileged user./tmp and the log dirs).HEALTHCHECK against /idp/status.Images are published to CSC's Artifactory registries and, on release, to Docker Hub as cscfi/shibboleth-idp. The image is built once on a release* branch push and promoted forward by exact digest, so every tier holds a bit-for-bit identical image.
Development images — pushed to the dev registry on every release* / dev* branch push:
| Tag | Example |
|---|---|
| commit anchor | sha-<full-commit-sha> |
| line + upstream | 2.x-5.2.3 (release branch) · dev-foo-5.2.3 (test branch) |
Release images — staging/prod/Docker Hub, triggered by a protected v<X.Y.Z> git tag. Tags combine the package version (the git tag, v stripped) with the upstream IdP version from the Dockerfile. For tag v2.0.0 on the 5.2.3 line:
| Tag | Example |
|---|---|
| major | 2 |
| major.minor | 2.0 |
| package version | 2.0.0 |
| package + upstream | 2.0.0-5.2.3 |
latest | only for the leading release line |
Each release line tags its own major (release1.x → v1.*, release2.x → v2.*).
The container exposes two ports:
8080 — HTTP (default). In production the IdP normally sits behind a TLS-terminating reverse proxy / ingress that speaks plain HTTP to this port.8443 — HTTPS, active only when ENABLE_SSL=1.podman run -d --name shibboleth-idp \
--memory 2g \
-p 8080:8080 \
cscfi/shibboleth-idp
Provide a PKCS12 keystore at /opt/jetty-base/etc/keystore (or set JETTY_KEYSTORE_PATH) and its password:
podman run -d --name shibboleth-idp \
--memory 2g \
-e ENABLE_SSL=1 \
-e JETTY_KEYSTORE_PASSWORD=<your-keystore-password> \
-p 8443:8443 \
-v /path/to/credentials:/opt/shibboleth-idp/credentials \
cscfi/shibboleth-idp
Enabling SSL adds the Jetty https + http2 modules and applies the TLS hardening in opt/jetty-base/etc/tls-config.xml (disables TLSv1.0/1.1 and weak cipher suites).
| Variable | Default | Purpose |
|---|---|---|
ENABLE_SSL | 0 | 1 enables the HTTPS connector on 8443 (requires a keystore). |
JETTY_KEYSTORE_PASSWORD | jkstorepwd | TLS keystore password. Override in production (env or secrets.properties); not baked as an image ENV. |
JETTY_KEYSTORE_PATH | ./etc/keystore | Keystore path, relative to the Jetty base. |
ENABLE_SEALER | 0 | 1 regenerates the session sealer (sealer.jks) at startup. |
FORCEBUILD | 0 | 1 runs build.sh after applying runtime customizations. |
CUSTOMIZED_IDP_DIR | /opt/shibboleth-idp-customized | Runtime overlay directory for IdP config (see below). |
CUSTOMIZED_JETTY_DIR | /opt/jetty-base-customized | Runtime overlay directory for Jetty config. |
IDP_LOG_HISTORY | 180 | Days of rolled audit/consent log history to keep (logback maxHistory). |
IDP_LOG_TOTAL_SIZE_CAP | 10GB | Total disk cap for rolled audit/consent files (logback totalSizeCap); oldest are deleted first. |
Heap sizing: the JVM is container-aware and sets the max heap to 75% of the container memory limit (-XX:MaxRAMPercentage=75.0). Set the container/pod memory limit (e.g. --memory 2g or resources.limits.memory) instead of a fixed -Xmx.
No volumes are strictly required, but you will typically mount:
/opt/shibboleth-idp/credentials — real signing/encryption keys, sealer and secrets.properties (see Credentials & secrets)./tmp, /opt/shibboleth-idp/logs, /opt/jetty-base/logs when running with a read-only root filesystem.You normally build your own image on top of this base. The recommended approach bakes your configuration at build time so the application directory can stay read-only:
FROM cscfi/shibboleth-idp
ADD shibboleth-idp/ /opt/shibboleth-idp/
# optionally: ADD jetty-base/ /opt/jetty-base/
A typical layout for your shibboleth-idp/ directory:
shibboleth-idp/
├── conf/ # idp.properties, metadata-providers.xml, relying-party.xml, attribute-*.xml, ...
├── credentials/ # idp-signing.*, idp-encryption.*, sealer.jks/kver, secrets.properties
├── metadata/ # idp-metadata.xml (or idp-metadata-template.xml), SP metadata
├── views/ # login.vm, logout.vm, ...
└── edit-webapp/ # images, css, web.xml overrides
Build it with:
podman build -t <org>/shibboleth-idp:<version> .
Runtime overlay (alternative): instead of baking, you can mount your config at CUSTOMIZED_IDP_DIR / CUSTOMIZED_JETTY_DIR; the entrypoint overlays it onto the base at startup. Note this requires those target directories to be writable, so it is less suited to a strict read-only root filesystem.
The base image is installed with the placeholder scope example.org / host idp.example.org. You must override the scope, hostname, entityID and credentials with your own configuration.
At startup the entrypoint reads /opt/shibboleth-idp/credentials/secrets.properties:
JETTY_* entries (e.g. JETTY_KEYSTORE_PASSWORD) configure the TLS connector.idp.sealer.* entries provide the sealer store password used when (re)generating sealer.jks.Other behaviors:
sealer.jks is missing (or ENABLE_SEALER=1), it is generated from the sealer password. When running multiple replicas, the same sealer must be shared across all pods (mount one Secret).metadata/idp-metadata-template.xml exists, the entrypoint generates idp-metadata.xml by injecting the signing certificate.To keep secrets out of your image, do not bake the credentials/ directory — mount it at runtime (-v <host>/credentials:/opt/shibboleth-idp/credentials) or supply it as a Kubernetes Secret.
Use deploy/kubernetes.yaml (works with both kubectl apply -f and oc apply -f). It sets a strict securityContext (runAsNonRoot, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: drop [ALL], seccompProfile: RuntimeDefault), liveness/readiness/startup probes against /idp/status, and emptyDir volumes for the writable paths.
For single-host deployments, two Quadlet units are provided — pick the model (see deploy/):
deploy/shibboleth-idp-overlay.container — runtime overlay: writable app dir, config applied at startup from mounted CUSTOMIZED_* dirs.deploy/shibboleth-idp-readonly.container — read-only rootfs: config baked into a child image by deploy/shibboleth-idp-build.service.Quadlet generates a systemd service from the unit file (requires podman ≥ 4.4):
# rootless (recommended), e.g. as the gitlab-runner user:
cp deploy/shibboleth-idp-overlay.container ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start shibboleth-idp-overlay
Both units use LogDriver=k8s-file (so podman logs works without journal access) and apply the image's hardening (dropped capabilities, no new privileges; the read-only unit adds ReadOnly=true + tmpfs for writable paths). Only one model runs at a time — they share ContainerName/ports, and Conflicts= enforces mutual exclusion.
Shibboleth IdP is a WAR webapp running inside Jetty — one JVM, one process, one container — so the streams cannot be split into separate services. Instead each stream is prefixed with its own token in the log pattern, so it stays individually filterable in podman logs:
| Stream | Prefix | Filter |
|---|---|---|
| Jetty (server + access) | jetty | podman logs shibboleth-idp | grep '^jetty ' |
| IdP application log | shibboleth-idp | podman logs shibboleth-idp | grep '^shibboleth-idp ' |
| IdP audit log | shibboleth-idp-audit | podman logs shibboleth-idp | grep '^shibboleth-idp-audit ' |
| IdP consent audit | shibboleth-idp-consent | podman logs shibboleth-idp | grep '^shibboleth-idp-consent ' |
| Everything from the IdP | — | podman logs shibboleth-idp |
The prefix is on the console (stdout) copies, which LogDriver=k8s-file captures to a per-container log file that podman logs reads.
Audit retention is the logback file appenders, not the stdout stream. With a persistent volume at /opt/shibboleth-idp/logs, idp-audit / idp-consent are rotated and kept for IDP_LOG_HISTORY days (capped by IDP_LOG_TOTAL_SIZE_CAP) — see the runtime variable table above. Without that volume the files are ephemeral, so mount one (or ship the stream to log aggregation) for compliance. The stdout log rotates only by size (--log-opt max-size) and resets when the container is recreated, so it is for operational tailing, not retention.
If you prefer central journald collection, set
LogDriver=journaldon the unit and filter by the same prefixes withjournalctl -t shibboleth-idp -g '^shibboleth-idp-audit '. Reading the journal then requires journal access (see below) — which is whyk8s-fileis the default here.
With LogDriver=k8s-file, podman logs shibboleth-idp reads a per-container log file under the owning user's container storage — no journal access is required. The rootless owner (e.g. the gitlab-runner user) reads its own container's logs directly; other unprivileged users cannot.
This is the main reason k8s-file is the default here: with LogDriver=journald the logs land in the journal, and an unprivileged user can read them only via journalctl --user (if a persistent user journal is enabled) or by being in the systemd-journal/adm group (usermod -aG systemd-journal <user>).
git clone ssh://[email protected]:10022/asso/Shibboleth-idp-containerized.git
cd Shibboleth-idp-containerized
podman build -f Dockerfile --no-cache -t shibboleth-idp .
The GitLab pipeline (.gitlab-ci.yml) uses three registry tiers + tag-driven production. The image is built once and promoted by exact digest, so the promoted image is bit-for-bit identical across registries:
push release*/dev* ─ build-idp → scan-image → push-idp-dev (dev)
git tag v<X.Y.Z> ─ push-idp-staging (auto) → push-idp-prod (manual) → push-idp-dockerhub (manual)
release* (or dev*) branch builds, scans and pushes to the dev registry, anchored by a sha-<commit> tag.v<X.Y.Z> git tag promotes that exact dev image to staging automatically, then to prod and Docker Hub behind manual buttons — all by digest, no rebuild.release/dev branches are only built and scanned (no push), keeping the dev registry clean.Because GitLab reads .gitlab-ci.yml from the ref being run, the pipeline must be present on each release* branch to govern it.
Releases come from
v*tags onrelease*branches.release2.xis the default branch (active line),release1.xthe older maintained line;masteris a frozen legacy (5.2.2) snapshot.
Jetty logs (including the access log) and the Shibboleth IdP idp-process.log are written to stdout only and exposed via podman logs (and the platform's log collection on Kubernetes/OpenShift). The high-volume process and access logs use async appenders for throughput. Audit and consent logs are additionally written to files under /opt/shibboleth-idp/logs.
Logging is configured in opt/shibboleth-idp/conf/logback.xml (IdP) and opt/jetty-base/resources/logback.xml (Jetty); overlay your own versions to change this behavior.
Maintained by CSC (IT Center for Science, Finland):
[email protected])The contents of the built image are subject to their respective licenses. The project files are licensed under the Apache License, Version 2.0:
Copyright 2019 Unicon, Inc.
Copyright 2020-> CSC – IT Center for Science Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Content type
Image
Digest
sha256:d6c18c42a…
Size
254.8 MB
Last updated
19 days ago
docker pull cscfi/shibboleth-idp