Sign inSign up

cscfi/shibboleth-idp

By cscfi

•Updated 19 days ago

Shibboleth IdP containerized (with bundled plugins)

Image
Security
0

4.4K

cscfi/shibboleth-idp repository overview

⁠Shibboleth IdP — containerized

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.

⁠Overview

Every component below is verified against a vendor SHA256 hash recorded in the Dockerfile, making the build essentially deterministic:

ComponentVersion
Shibboleth IdP5.2.3
Jetty12.1.13
OpenJDK (headless)21 (LTS)
Alpine base3.24.2 (pinned)
MariaDB JDBC client3.5.10
PostgreSQL JDBC driver42.7.13

Bundled plugins:

PluginVersion
oidc-common3.3.1
oidc-config3.0.1
oidc-op4.3.2
oidc-rp2.3.0
WebAuthn1.4.2
JDBC storage2.1.0
GEANT user-profile1.2.1
OAuth2 device grant0.9.3
Step-up authn2.0.0
Reverse-proxy authn2.0.0
Authn discovery2.3.0
Nashorn scripting2.0.0
CandourID1.0.0
csc-library0.14.0

Versions above are a snapshot; the Dockerfile is the source of truth (Renovate bumps the version ARGs there).

⁠Security & hardening

The image is built to run unprivileged on podman, Kubernetes and OpenShift:

  • Non-root, arbitrary UID. Files are owned 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.
  • Read-only root filesystem capable (with writable volumes for /tmp and the log dirs).
  • Pinned base images, minimal runtime packages, and a HEALTHCHECK against /idp/status.
  • No baked runtime secrets: the build-time passwords generate example/self-signed credentials only — you must supply real credentials in production.

⁠Image tags & registries

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:

TagExample
commit anchorsha-<full-commit-sha>
line + upstream2.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:

TagExample
major2
major.minor2.0
package version2.0.0
package + upstream2.0.0-5.2.3
latestonly for the leading release line

Each release line tags its own major (release1.x → v1.*, release2.x → v2.*).

⁠Running the image

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.
⁠HTTP (behind a TLS-terminating proxy / for local testing)
podman run -d --name shibboleth-idp \
  --memory 2g \
  -p 8080:8080 \
  cscfi/shibboleth-idp
⁠HTTPS (terminate TLS in the container)

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).

⁠Runtime environment variables
VariableDefaultPurpose
ENABLE_SSL01 enables the HTTPS connector on 8443 (requires a keystore).
JETTY_KEYSTORE_PASSWORDjkstorepwdTLS keystore password. Override in production (env or secrets.properties); not baked as an image ENV.
JETTY_KEYSTORE_PATH./etc/keystoreKeystore path, relative to the Jetty base.
ENABLE_SEALER01 regenerates the session sealer (sealer.jks) at startup.
FORCEBUILD01 runs build.sh after applying runtime customizations.
CUSTOMIZED_IDP_DIR/opt/shibboleth-idp-customizedRuntime overlay directory for IdP config (see below).
CUSTOMIZED_JETTY_DIR/opt/jetty-base-customizedRuntime overlay directory for Jetty config.
IDP_LOG_HISTORY180Days of rolled audit/consent log history to keep (logback maxHistory).
IDP_LOG_TOTAL_SIZE_CAP10GBTotal 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.

⁠Volumes

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).
  • writable volumes for /tmp, /opt/shibboleth-idp/logs, /opt/jetty-base/logs when running with a read-only root filesystem.

⁠Customizing the IdP

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.

⁠Credentials & secrets

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: if 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: if 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.

⁠Running on Kubernetes / OpenShift

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.

⁠Running with Podman Quadlet (systemd)

For single-host deployments, two Quadlet units are provided — pick the model (see deploy/⁠):

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.

⁠Separating Jetty and Shibboleth logs

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:

StreamPrefixFilter
Jetty (server + access)jettypodman logs shibboleth-idp | grep '^jetty '
IdP application logshibboleth-idppodman logs shibboleth-idp | grep '^shibboleth-idp '
IdP audit logshibboleth-idp-auditpodman logs shibboleth-idp | grep '^shibboleth-idp-audit '
IdP consent auditshibboleth-idp-consentpodman 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=journald on the unit and filter by the same prefixes with journalctl -t shibboleth-idp -g '^shibboleth-idp-audit '. Reading the journal then requires journal access (see below) — which is why k8s-file is the default here.

⁠Who can read the logs?

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>).

⁠Building from source

git clone ssh://[email protected]:10022/asso/Shibboleth-idp-containerized.git
cd Shibboleth-idp-containerized
podman build -f Dockerfile --no-cache -t shibboleth-idp .

⁠CI/CD pipeline

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)
  • A push to a release* (or dev*) branch builds, scans and pushes to the dev registry, anchored by a sha-<commit> tag.
  • Creating a protected 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.
  • MRs and non-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 on release* branches. release2.x is the default branch (active line), release1.x the older maintained line; master is a frozen legacy (5.2.2) snapshot.

⁠Logging

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.

⁠Maintainer

Maintained by CSC (IT Center for Science, Finland):

⁠License

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.

Tag summary

Content type

Image

Digest

sha256:d6c18c42a…

Size

254.8 MB

Last updated

19 days ago

docker pull cscfi/shibboleth-idp