Compare commits

..

87 Commits

Author SHA1 Message Date
electron 2616d040d0 docs: add DEPLOYMENT.md for saillant.cc instance
- SAML entity in Renater test federation
- Metadata URL, OIDC clients (moodle-lms, frappe-lms)
- SATOSA 8.5.1 container config

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:37:55 +01:00
Jonathan Perret 5932135ec5 🔐(secrets) update secrets to current main 2026-03-26 15:26:17 +01:00
Jonathan Perret 0a43908ac3 Deploy release v1.0.13 to production 2026-03-26 15:10:43 +01:00
Jonathan Perret 435e2e20a9 v1.0.13 release 2026-03-26 14:54:48 +01:00
Jonathan Perret 5dcfcf213d Merge pull request #40 from proconnect-gouv/stateless-oidc
Utiliser le mode stateless de SATOSA pour ne plus avoir besoin de Redis
2026-03-26 14:53:06 +01:00
Jonathan Perret 00b048d94d 🗃️(redis) remove Redis support
We switched to stateless OIDC.
2026-03-26 14:34:58 +01:00
Jonathan Perret bda7a44bc8 🔧(helm) use stateless OIDC in production
Getting rid of Redis should help with performance.
2026-03-26 13:41:53 +01:00
Jonathan Perret 9db60451aa 🔧(helm) use stateless OIDC in dev and staging
Getting rid of Redis should help with performance.
2026-03-25 10:37:00 +01:00
Jonathan Perret 018b9e8726 🔧(helm) use lists for env vars
Moving back to the Kubernetes-native syntax of lists makes it possible
to use dependent environment variables.
2026-03-25 10:04:28 +01:00
Jonathan Perret ea4199de91 Merge pull request #39 from proconnect-gouv/tilt-oidc-test-client
🔨 Configuration Tilt pour le client test OIDC
2026-03-20 15:44:31 +01:00
Jonathan Perret 4d1a9c56d2 🔨(e2e) add benchmark e2e test
This can help diagnose performance issues.
2026-03-20 15:32:09 +01:00
Jonathan Perret 14f9d42889 ⬆️ (oidc-test-client) create Helm chart for test client
This helps running tests with the app running in a
Kubernetes cluster.
2026-03-20 15:32:08 +01:00
Jonathan Perret 3a0a0564d7 🔧(siret) fix SIRET for univ-angers.fr
As provided by RENATER.
2026-03-06 17:24:28 +01:00
Jonathan Perret 9a82204f69 🔧(siret) add new entities to SIRET map
As provided by RENATER.
2026-03-05 15:22:10 +01:00
Jonathan Perret fd4a2b665f 🔧(siret) add new entities to SIRET map
As provided by RENATER.
2026-03-05 14:11:14 +01:00
Jonathan Perret 645bbaca74 Deploy release v1.0.12 to production 2026-03-02 19:43:36 +01:00
Jonathan Perret 1ef50896ec v1.0.12 release 2026-03-02 19:41:37 +01:00
Jonathan Perret f8661c76d5 Merge pull request #38 from proconnect-gouv/deps-update
Update dependencies
2026-03-02 19:38:16 +01:00
Jonathan Perret ecfa38f11e ⬆️ (actions) upgrade and pin GitHub actions
To latest stable versions.
2026-02-27 19:08:46 +01:00
Jonathan Perret 159d992783 🔧(config) add healthcheck to docker-compose
This makes docker-compose up --wait work.
2026-02-27 19:05:31 +01:00
Jonathan Perret 2efaaa8281 🔨(kind) improve local cluster start script
Taking some cues from what's done in other projects.
2026-02-27 19:05:30 +01:00
Jonathan Perret f0b1c4cbc3 ⬆️ (python) upgrade Python dependencies
To latest stable versions.
2026-02-27 19:05:29 +01:00
Jonathan Perret 2e9dd01907 🔨(redis) fix bitnami image reference
Bitnami images have moved.
2026-02-27 19:05:28 +01:00
Jonathan Perret 5f7b7ab8a8 🔥(pyproject) remove setup.py
This file is not needed with a modern project structure, and since
`setuptools` is no longer included in recent Python versions it causes a
`pylint` failure.
2026-02-27 19:05:19 +01:00
Jonathan Perret 9994eb84b0 ⬆️ (python) upgrade to Python 3.14.3
Bumping base image to avoid reported vulnerabilities.
2026-02-27 19:04:37 +01:00
Jonathan Perret f9efafd574 🔧 (metadata) update Renater metadata URL
The old URLs were decommissionned on feb. 26.
2026-02-27 14:45:11 +01:00
Jonathan Perret 208c4467df 🔐(secrets) update secrets to current main 2026-02-11 10:35:53 +01:00
Jonathan Perret 0e82e8b771 🔧(siret) fix stray space in SIRET map
This was preventing EHESP users from connecting since
their IdP's entity ID was not matched.
2025-12-03 23:14:01 +01:00
Jonathan Perret 798afdf2a9 Deploy release v1.0.11 to production 2025-11-24 18:06:22 +01:00
Jonathan Perret 7cf9fda7d8 v1.0.11 release 2025-11-24 18:05:40 +01:00
Jonathan Perret 1816d2a8de Merge pull request #36 from proconnect-gouv/update-setuptools
⬆️(setuptools) remove setuptools from production Docker image
2025-11-24 18:04:18 +01:00
Jonathan Perret c89b9b7ada ⬆️(setuptools) remove setuptools from production Docker image
To fix a CVE reported by Cyberwatch. These tools are not required at
runtime so there's no need to keep them around.
2025-11-24 17:51:02 +01:00
Jonathan Perret c0d252f390 🔧(siret) fix entity ID for two entities
As provided by RENATER.
2025-11-24 11:47:36 +01:00
Jonathan Perret fa39964a64 🔧(siret) add new entities to SIRET map
As provided by RENATER.
2025-11-24 09:42:00 +01:00
Jonathan Perret 44b9879a3b Deploy release v1.0.10 to production 2025-11-21 11:10:07 +01:00
Jonathan Perret 7c3a0fccc9 v1.0.10 release 2025-11-21 11:07:03 +01:00
Jonathan Perret c224e31a66 Merge pull request #35 from rouja/helm-refacto
⬆️(helmfile) rename helmfile to be compatible with new helmfile plugin
2025-11-20 16:09:14 +01:00
Jacques ROUSSEL 4892192b85 ⬆️(helmfile) rename helmfile.yaml to helmfile.yaml.gotmpl
For compatibility with helmfile v1.x.
2025-11-20 16:02:50 +01:00
Jonathan Perret 79aee346a2 🔧(siret) update SIRET map to replace closed entities
These new SIRET numbers were found using
https://annuaire-entreprises.data.gouv.fr/
2025-10-23 10:21:57 +02:00
Jonathan Perret 4e84ec0f2b Deploy release v1.0.9 to production 2025-10-03 13:22:03 +02:00
Jonathan Perret 3401da37ff v1.0.9 release 2025-10-03 13:19:29 +02:00
Jonathan Perret 1124893498 Merge pull request #34 from proconnect-gouv/add-siret-mapping 2025-10-03 13:16:09 +02:00
Jonathan Perret 1557f7ba22 🔧(siret) add initial SIRET map to production environment
This mapping was provided by RENATER on 2025-09-19.
2025-10-03 13:10:40 +02:00
Jonathan Perret 6c344fae05 (siret) introduce SIRET mapping from EntityID
The proxy now uses a SIRET_MAP environment variable that must contain
a JSON mapping of SAML entity IDs to SIRET numbers.
These SIRET numbers will be returned to OIDC clients that request the
`siret` scope, in the `siret` claim.
2025-10-02 19:50:40 +02:00
Jonathan Perret cb8352787d Merge pull request #32 from proconnect-gouv/fix-acr-claim 2025-09-17 17:31:38 +02:00
Jonathan Perret 9a33149e9e (e2e) adapt to ProConnect error page wording change
The generic message was replaced by the error code
and description from the IdP.
2025-09-17 16:06:37 +02:00
Jonathan Perret a56ce17b34 ⬆️(oidc-test-client) upgrade oic to 1.7.0
This mainly avoids a conflict with the oidc2fer requirements
when installing both in a local virtualenv for IDE support.
2025-09-17 16:06:36 +02:00
Jonathan Perret a8bf8f61b4 🐛(oidc) stop forcing id_token 'acr' claim
There is a regression in pyop 3.4.2 which causes the configured
extra_id_token_claims to be ignored. Fortunately, ProConnect Core
now requests the required `acr` claim explicitly, so it is no longer
necessary to force it.

The OIDC test client was updated to request the `acr` claim in the
same way that ProConnect does it.
2025-09-17 16:06:35 +02:00
Jonathan Perret 7b1571b0ed (e2e) fix e2e test to more reliably select test IdP
The list of IdPs on the RENATER test federation seems to have
grown enough that the test IdP we want is no longer displayed
by default, so we have to search for it.
2025-09-15 12:15:05 +02:00
Jonathan Perret 3ecbf1a5a0 Fix docker registry for production 2025-09-10 18:23:52 +02:00
Jonathan Perret 445485ecc5 Deploy release 1.0.8 to production 2025-09-10 18:19:49 +02:00
Jonathan Perret 1bceed1373 v1.0.8 release 2025-09-10 18:08:39 +02:00
Jonathan Perret 850636970c 🚀(staging) add missing ghcr.io prefix to image name
Without a registry name, Docker Hub is still the default.
2025-09-10 18:06:02 +02:00
Jonathan Perret 9a1803b3e6 Merge pull request #31 from proconnect-gouv/move-to-proconnect 2025-09-10 17:56:47 +02:00
Jonathan Perret c51c95e007 💚(ci) fix CI jobs post org move
- use the GitHub app on numerique-gouv only to access the secrets repo
- inline helmfile-lint action (it only works on repos in numerique-gouv
  currently)
- replace deprecated `git whatchanged` with `git log`
- publish to ghcr.io instead of Docker Hub
- rename workflows to match other proconnect-gouv repos
2025-09-10 17:40:32 +02:00
Jonathan Perret 2687868465 📝(repo) update remaining references to numerique-gouv
Also updating CHANGELOG.
2025-09-10 17:40:30 +02:00
Jonathan Perret 38707412b3 🔧(secrets) change secrets submodule path
Since the oidcfer repo moved to a different organization the relative
path to the secrets submodule needs to change.
2025-09-10 15:24:56 +02:00
Jonathan Perret 8cd89621b2 Deploy release v1.0.7 to production. 2025-05-07 17:08:42 +02:00
Jonathan Perret 425365d22a v1.0.7 release 2025-05-07 17:07:48 +02:00
Jonathan Perret 63806bde22 Merge pull request #30 from numerique-gouv/fix-cve
Remove unused APT dependencies that trigger a CVE warning
2025-05-07 13:46:13 +02:00
Jonathan Perret 96299e1386 ⬆️ (deps) Bump gunicorn to 23.0.0 2025-05-07 13:43:45 +02:00
Jonathan Perret bf015c9238 🔒️(docker) remove unused APT dependencies that trigger a CVE warning
Specifically CVE-2023-45853 was flagged in our Docker image, but
it is in libfreetype6 which we don't use, and is only pulled in
because of a recommended depdency of `gcc` — which we don't need
in any case.
2025-05-07 13:43:45 +02:00
Jacques ROUSSEL 589d447f19 🐛(ci) fix helmfile linter
Latest helmfile version breaks lots of thing.
2025-03-28 14:46:38 +01:00
Jacques ROUSSEL ddc09cca50 🐛(ci) use github action for argocd webhook notification
In order to refactor this notification between alls projetcs, we
chooseto use a custom github action
2025-03-28 14:46:38 +01:00
Jonathan Perret ae2d4a573b Deploy release v1.0.6 to production. 2025-03-11 16:34:02 +01:00
Jonathan Perret 5be05fe43e v1.0.6 release 2025-03-11 16:31:50 +01:00
Jonathan Perret f90df5c2c6 Merge pull request #27 from numerique-gouv/allow-more-affiliations
Allow more affiliations
2025-03-11 16:02:10 +01:00
Jonathan Perret 6bbcd95069 🔧(config) allow more affiliations
We agreed with RENATER to allow people having one of the following
affiliations: faculty, staff, employee, researcher, teacher.
2025-03-11 15:42:27 +01:00
Jonathan Perret b839c32b50 Merge pull request #26 from numerique-gouv/satosa-8.5.1
⬆️ (deps) upgrade to SATOSA 8.5.1
2025-03-11 15:38:15 +01:00
Jonathan Perret 9b062aa27d ⬆️ (deps) upgrade to SATOSA 8.5.1 2025-03-11 15:23:36 +01:00
Jonathan Perret 4a2ece217b Deploy release v1.0.5 to production. 2025-03-06 19:48:00 +01:00
Jonathan Perret a7577ff85e v1.0.5 release 2025-03-06 19:38:28 +01:00
Jonathan Perret 4e81e1e750 Merge pull request #25 from numerique-gouv/custom-log-levels
🚀(logging) allow customizing log levels per logger
2025-03-06 19:36:05 +01:00
Jonathan Perret c27ebfe6ae 🚀(logging) allow customizing log levels per logger
This lets one enable increased logging at a finer level. For example,
one could enable debug logging for a specific logger, while keeping the
rest of the application at info level.
2025-03-06 19:27:01 +01:00
Jonathan Perret d84f2b32d5 Merge pull request #24 from numerique-gouv/filter-paths
🔒️(nginx) add basic path allowlist to block random attacks
2025-03-05 18:32:09 +01:00
Jonathan Perret 9cb67bfb87 🔒️(nginx) add basic path allowlist
This should cut down on opportunistic attack attempts, which are more
a nuisance than anything right now but it can't hurt to stop them
earlier.
2025-03-05 18:26:45 +01:00
Jonathan Perret ba1774bdbf Merge pull request #23 from numerique-gouv/fix-e2e-test
(e2e) fix e2e test with PC staging
2025-03-05 18:18:45 +01:00
Jonathan Perret cb8ff5d878 (e2e) fix e2e test with PC staging
Adapt to updated mock service.
2025-03-05 17:52:03 +01:00
rouja a6e01c071e Merge pull request #21 from numerique-gouv/add-pdbs
🔧(helm) add pdbs to deployements
2025-02-10 17:24:10 +01:00
Jacques ROUSSEL 5a0f2da202 🔧(helm) add pdbs to deployements
In order to avoid a service interruption during a Kubernetes
(k8s)upgrade, we add a Pod Disruption Budget (PDB) to deployments.
2025-02-10 17:19:08 +01:00
rouja 3b2d50d657 Merge pull request #20 from numerique-gouv/fix-issuer
fix letsencrypt issuer for new preprod
2025-02-07 11:53:59 +01:00
Jacques ROUSSEL 52d7de305c 🔧(helm) change letsencrypt issuer
The name of the cluster issuer on the new preprod is 'letsencrypt,' so I
adjusted it.
2025-02-07 10:49:36 +01:00
Jonathan Perret 64a0d4dd3d Merge pull request #19 from numerique-gouv/improve-helm 2024-10-02 10:46:04 +02:00
Jacques ROUSSEL ffbd161e29 (ci) add helmfile linter
Add a github job to run helmfile linter on PR
2024-09-24 17:11:13 +02:00
Jonathan Perret ba1c14291d Merge pull request #17 from numerique-gouv/add-architecture-doc
📝(architecture) add first architecture doc
2024-09-21 12:21:01 +02:00
Jonathan Perret 4c4ccc88ba 📝(architecture) add first architecture doc
This gives an overview and the detail of the workings of OIDC2FER.
2024-09-18 19:31:33 +02:00
Jonathan Perret c1b33453c3 Deploy release v1.0.4 to production. 2024-09-11 12:43:17 +02:00
49 changed files with 1134 additions and 370 deletions
+2 -2
View File
@@ -9,9 +9,9 @@ We primarily use GitHub as an issue tracker. If however you're encountering an i
---
Please make sure you have read our [main Readme](https://github.com/numerique-gouv/oidc2fer).
Please make sure you have read our [main Readme](https://github.com/proconnect-gouv/oidc2fer).
Also make sure it was not already answered in [an open or close issue](https://github.com/numerique-gouv/oidc2fer/issues).
Also make sure it was not already answered in [an open or close issue](https://github.com/proconnect-gouv/oidc2fer/issues).
If your question was not covered, and you feel like it should be, fire away! We'd love to improve our docs! 👌
+67
View File
@@ -0,0 +1,67 @@
name: 🐳 Build docker images
on:
push:
branches:
- 'main'
tags:
- 'v*'
pull_request:
branches:
- 'main'
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=sha,prefix=sha-
# set latest tag for default branch
type=raw,value=latest,enable={{is_default_branch}}
- name: Login to Github Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ github.token }}
- name: Build and push
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
target: production
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
notify-argocd:
runs-on: ubuntu-latest
if: |
github.event_name != 'pull_request'
needs:
- build-and-push
steps:
- uses: numerique-gouv/action-argocd-webhook-notification@cac2ee67896eb13e84e804f60c4271370424eaa8 # main
id: notify
with:
deployment_repo_path: "${{ github.repository }}"
argocd_webhook_secret: "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}"
argocd_url: "${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}"
@@ -1,4 +1,4 @@
name: OIDC2FER Workflow
name: 💚 CI Tests
on:
push:
@@ -16,14 +16,14 @@ jobs:
if: github.event_name == 'pull_request' # Makes sense only for pull requests
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: show
run: git log
- name: Enforce absence of print statements in code
run: |
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude)**/oidc2fer.yml' | grep "print("
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude).github/**' | grep "print("
- name: Check absence of fixup commits
run: |
! git log | grep 'fixup!'
@@ -39,17 +39,17 @@ jobs:
github.event_name == 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Check that the CHANGELOG has been modified in the current branch
run: git whatchanged --name-only --pretty="" origin/${{ github.event.pull_request.base.ref }}..HEAD | grep CHANGELOG
run: git log --name-only --pretty="" origin/${{ github.event.pull_request.base.ref }}..HEAD | grep CHANGELOG
lint-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Check CHANGELOG max line length
run: |
max_line_length=$(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com" | wc -L)
@@ -65,11 +65,11 @@ jobs:
working-directory: src/satosa
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Python
uses: actions/setup-python@v3
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.11'
python-version: '3.14'
- name: Install development dependencies
run: |
# Python's xmlsec requirement
@@ -89,11 +89,11 @@ jobs:
working-directory: src/satosa
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Python
uses: actions/setup-python@v3
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.11'
python-version: '3.14'
- name: Install development dependencies
run: |
# Python's xmlsec requirement
@@ -101,3 +101,45 @@ jobs:
pip install --user .[dev]
- name: Run tests
run: ~/.local/bin/pytest
helmfile-lint:
runs-on: ubuntu-latest
# skip this job on forks since they won't have access to the required secrets
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'
container:
image: ghcr.io/helmfile/helmfile:v1.2.0
steps:
-
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
id: app-token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: numerique-gouv
repositories: secrets
-
name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
submodules: recursive
token: ${{ steps.app-token.outputs.token }}
-
name: Install mkcert
run: |
apt-get update -y -q && apt-get install -y -q libnss3-tools
curl -Ss -L -o mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
chmod +x mkcert
mv mkcert /usr/local/bin/
TRUST_STORES=none mkcert -install
-
name: Helmfile lint
env:
SOPS_AGE_KEY: ${{ secrets.SOPS_PRIVATE }}
run: |
HELMFILE=src/helm/helmfile.yaml.gotmpl
environments=$(< $HELMFILE awk -F'[ :]+' '/^ [^ ]/ {print $2} /^---/ { exit }')
for env in $environments; do
echo "################### $env lint ###################"
helmfile -e $env -f "$HELMFILE" lint || exit 1
echo -e "\n"
done
-93
View File
@@ -1,93 +0,0 @@
name: Docker Hub Workflow
on:
workflow_dispatch:
push:
branches:
- 'main'
tags:
- 'v*'
pull_request:
branches:
- 'main'
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
-
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: "oidc2fer,secrets"
-
name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ steps.app-token.outputs.token }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: lasuite/oidc2fer
-
name: Load sops secrets
uses: rouja/actions-sops@main
with:
secret-file: secrets/numerique-gouv/oidc2fer/secrets.enc.env
age-key: ${{ secrets.SOPS_PRIVATE }}
-
name: Login to DockerHub
run: echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_USER" --password-stdin
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
target: production
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
notify-argocd:
runs-on: ubuntu-latest
if: |
github.event_name != 'pull_request'
needs:
- build-and-push
steps:
-
uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: "oidc2fer,secrets"
-
name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ steps.app-token.outputs.token }}
-
name: Load sops secrets
uses: rouja/actions-sops@main
with:
secret-file: secrets/numerique-gouv/oidc2fer/secrets.enc.env
age-key: ${{ secrets.SOPS_PRIVATE }}
-
name: Call argocd github webhook
run: |
data='{"ref": "'$GITHUB_REF'","repository": {"html_url":"'$GITHUB_SERVER_URL'/'$GITHUB_REPOSITORY'"}}'
sig=$(echo -n ${data} | openssl dgst -sha1 -hmac ''${ARGOCD_WEBHOOK_SECRET}'' | awk '{print "X-Hub-Signature: sha1="$2}')
curl -X POST -H 'X-GitHub-Event:push' -H "Content-Type: application/json" -H "${sig}" --data "${data}" $ARGOCD_WEBHOOK_URL
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "secrets"]
path = secrets
url = ../secrets
url = ../../numerique-gouv/secrets
+33
View File
@@ -6,6 +6,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.13] - 2026-03-26
- switch to stateless OIDC and remove Redis
## [1.0.12] - 2026-03-02
- upgrade dependencies: Python 3.14.3, gunicorn 25.1.0…
## [1.0.11] - 2025-11-24
- remove setuptools, wheel and pip from production Docker image
## [1.0.10] - 2025-11-21
- rename helmfile.yaml for compatibility with helmfile 1.x
## [1.0.9] - 2025-10-03
- map SAML entity IDs to SIRET (#34)
- include ACR claim in ID token only if requested (#32)
## [1.0.8] - 2025-09-10
- fix secrets submodule path following move to `proconnect-gouv` organization
- update other references to `numerique-gouv` organization
- publish Docker images at ghcr.io/proconnect-gouv/oidc2fer
## [1.0.7] - 2025-05-07
- remove unused APT dependencies
- upgrade to gunicorn 23.0.0
## [1.0.6] - 2025-03-11
- upgrade to SATOSA 8.5.1
- allow faculty, staff, employee, researcher, teacher affiliations
## [1.0.5] - 2025-03-06
- add allowlist for URL paths to nginx ingress
- allow customizing log levels per logger
## [1.0.4] - 2024-09-11
- serve static files
+32
View File
@@ -0,0 +1,32 @@
# oidc2fer -- Deploiement saillant.cc
Fork de [proconnect-gouv/oidc2fer](https://github.com/proconnect-gouv/oidc2fer) maintenu sur [electron-rare/oidc2fer](https://github.com/electron-rare/oidc2fer).
## Federation SAML Renater
- Entite SAML enregistree dans la **federation de test Renater**
- Metadata URL : https://pub.federation.renater.fr/metadata/test/idps.xml
## Clients OIDC configures
| Client | Usage |
|--------|-------|
| moodle-lms | Plateforme LMS Moodle |
| frappe-lms | Plateforme LMS Frappe |
## Container
- **Nom** : oidc2fer
- **Image** : oidc2fer-app (build local depuis le Dockerfile)
## Stack technique
- **SATOSA 8.5.1** -- proxy SAML-to-OIDC
- Bridge entre les IdP SAML de la federation Renater et les clients OIDC locaux
## Demarrage
```bash
cd /home/clems/oidc2fer
docker compose -f docker-compose.prod.yml up -d
```
+16 -13
View File
@@ -1,25 +1,23 @@
# ---- base image to inherit from ----
FROM python:3.11-slim-bookworm as common
FROM python:3.14.3-slim-trixie AS common
# Install xmlsec1 dependencies required for xmlsec (for SAML)
# Needs to be kept before the `pip install`
RUN apt-get update && \
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get -y upgrade && \
apt-get install -y \
pkg-config \
gcc \
xmlsec1 \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl && \
apt-get install -qy --no-install-recommends xmlsec1 && \
rm -rf /var/lib/apt/lists/*
# Silence pip warnings about running as root in a container
ENV PIP_ROOT_USER_ACTION=ignore
# We want the most up-to-date stable pip release
RUN pip install --upgrade pip
ENV PYTHONUNBUFFERED=1
# Give the "root" group the same permissions as the "root" user on /etc/passwd
# Give the "root" group the same permissions AS the "root" user on /etc/passwd
# to allow a user belonging to the root group to add new users; typically the
# docker user (see entrypoint).
RUN chmod g=u /etc/passwd
@@ -41,7 +39,11 @@ COPY docker/files/usr/local/etc/gunicorn/satosa.py /usr/local/etc/gunicorn/satos
CMD ["gunicorn", "-c", "/usr/local/etc/gunicorn/satosa.py"]
# ---- Development image ----
FROM common as development
FROM common AS development
# Install curl (for healthchecks)
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && apt-get install -qy curl
# Playwright browsers
ENV PLAYWRIGHT_BROWSERS_PATH=/pw-browsers
@@ -67,14 +69,15 @@ COPY ./src/satosa /app/
USER ${DOCKER_USER}
# ---- Production image (keep last so it is the default target) ----
FROM common as production
FROM common AS production
# Copy oidc2fer application (see .dockerignore)
COPY ./src/satosa /app/
WORKDIR /app
RUN pip install .
# Uninstall pip, setuptools and wheel after installation to reduce attack surface
RUN pip install . && pip uninstall -y setuptools wheel pip
# Switch to unprivileged user
USER ${DOCKER_USER}
+8 -1
View File
@@ -57,12 +57,19 @@ number and date.
## Deploying a release to production (DINUM instance)
1. Make sure the release you want to deploy has been built and appears on
https://hub.docker.com/r/lasuite/oidc2fer/tags .
https://github.com/proconnect-gouv/oidc2fer/pkgs/container/oidc2fer
2. Edit `image/tag` at the top of
`src/helm/env.d/outscale-production/values.oidc2fer.yaml.gotmpl`.
3. Commit and push to `main`.
4. Update the `production` tag and push it.
## Environment variables
| variable | usage |
| --- | --- |
| `LOG_LEVEL` | Sets the log level for the root logger, i.e. the default. Defaults to `INFO`. |
| `LOG_LEVELS` | A JSON object that can be used to set log levels for specific loggers, e.g. `{"satosa.backends.saml2": "DEBUG"}`. Defaults to `{}`. |
## Contributing
This project is intended to be community-driven, so please, do not hesitate to
+5
View File
@@ -13,6 +13,11 @@ docker_build(
]
)
docker_build(
'localhost:5001/oidc-test-client:latest',
context='docker/oidc-test-client'
)
watch_file('src/helm')
k8s_yaml(local('cd src/helm && helmfile -n oidc2fer -e dev template .'))
+11 -9
View File
@@ -13,8 +13,8 @@ services:
BASE_URL: https://satosa.traefik.me
GUNICORN_CMD_ARGS: --workers=2
TEST_E2E: 1
#TEST_E2E_AC: 1
OIDC_DB_URI: redis://redis/0
#TEST_E2E_PC: 1
OIDC_DB_URI: stateless://:STATE-ENCRYPTION-KEY@localhost
CLIENT_DB_JSON: |
{
"oidc-test-client": {
@@ -30,7 +30,7 @@ services:
}
}
SAML2_DISCOVERY_URL: https://discovery.renater.fr/test/
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/test/preview/preview-idps-test-metadata.xml
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/test/idps.xml
SAML2_ENTITY_ID: https://satosa.traefik.me/Saml2/proxy_saml2_backend.xml
env_file:
- env.d/development/common
@@ -38,8 +38,13 @@ services:
volumes:
- ./src/satosa:/app
- ./docker/files/usr/local/etc/gunicorn/satosa.py:/usr/local/etc/gunicorn/satosa.py
depends_on:
- redis
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8000/ping"]
interval: 60s
timeout: 5s
retries: 3
start_period: 30s
start_interval: 1s
oidc-test-client:
build: docker/oidc-test-client
@@ -66,13 +71,10 @@ services:
#OIDC_PROVIDER: https://oidc2fer-staging.beta.numerique.gouv.fr
OIDC_CLIENT_ID: oidc-test-client
OIDC_CLIENT_SECRET: oidc-test-secret
OIDC_SCOPES: openid,uid,given_name,usual_name,email
OIDC_SCOPES: openid,uid,given_name,usual_name,email,siret
extra_hosts:
- "oidc2fer.127.0.0.1.nip.io:host-gateway"
redis:
image: redis:7.2.4
nginx:
image: nginx:1.25
ports:
-6
View File
@@ -39,11 +39,5 @@ printenv SAML2_BACKEND_KEY > /tmp/backend.key
printenv OIDC_FRONTEND_KEY > /tmp/frontend.key
printenv CLIENT_DB_JSON > /tmp/client_db.json
# Redis database number must be specified
if [[ ! $OIDC_DB_URI =~ /[0-9]+$ ]]; then
echo "🐳(entrypoint) adding db id to OIDC_DB_URI"
export OIDC_DB_URI=$OIDC_DB_URI/0
fi
echo "🐳(entrypoint) running your command: ${*}"
exec "$@"
+36 -1
View File
@@ -1,4 +1,5 @@
import datetime
import json
import logging
import os
import sys
@@ -29,6 +30,8 @@ class RequestJSONFormatter(json_log_formatter.JSONFormatter):
url += f"?{record.args['q']}"
return {
"logger": record.name,
"level": record.levelname,
"remote_ip": record.args["h"],
"method": record.args["m"],
"path": url,
@@ -37,7 +40,7 @@ class RequestJSONFormatter(json_log_formatter.JSONFormatter):
"user_agent": record.args["a"],
"referer": record.args["f"],
"duration_in_ms": record.args["M"],
"poppid": record.process,
"pid": record.process,
}
@@ -55,11 +58,23 @@ class DefaultJSONFormatter(json_log_formatter.JSONFormatter):
payload: dict[str, str | int | float] = super().json_record(
message, extra, record
)
payload["logger"] = record.name
payload["level"] = record.levelname
payload["pid"] = record.process
return payload
class NoPingFilter(logging.Filter):
"""
Filters out /ping requests from the access log.
"""
def filter(self, record: logging.LogRecord) -> bool:
return not( record.args.get("m", "") == "GET"
and record.args.get("U", "") == "/ping"
and str(record.args.get("s", "")) == "200" )
bind = ["0.0.0.0:8000"]
name = "satosa"
python_path = "/app"
@@ -76,6 +91,9 @@ accesslog = "-"
errorlog = "-"
loglevel = os.environ.get("LOG_LEVEL", "INFO")
loglevels_json = os.environ.get("LOG_LEVELS", "{}")
loglevels = json.loads(loglevels_json)
logconfig_dict = {
"version": 1,
"disable_existing_loggers": True,
@@ -95,7 +113,24 @@ logconfig_dict = {
"handlers": ["json_request"],
"propagate": False,
"qualname": "gunicorn.access",
"filters": [NoPingFilter()],
},
"satosa.base": {
"level": "INFO",
},
"satosa.state": {
"level": "INFO",
},
"satosa.proxy_server": {
"level": "INFO",
},
"satosa.routing": {
"level": "INFO",
},
"satosa.frontends.ping": {
"level": "INFO",
},
**{k: {"level": v.upper()} for k, v in loglevels.items()},
},
"formatters": {
"json_request": {
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.11
FROM python:3.14.3-slim-trixie
COPY . /app
+2 -4
View File
@@ -2,13 +2,10 @@ from flask import Flask, jsonify, request, session
from oic.oic import Client
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
from oic import rndstr
from oic.oic.message import RegistrationResponse
from oic.oic.message import Claims, ClaimsRequest, RegistrationResponse
from oic.utils.http_util import Redirect
from oic.oic.message import AuthorizationResponse
import secrets
import webbrowser
import threading
import time
import logging
import os
@@ -52,6 +49,7 @@ def index():
"nonce": session["nonce"],
"redirect_uri": client.registration_response["redirect_uris"][0],
"state": session["state"],
"claims": ClaimsRequest(id_token=Claims(acr=None, amr=None)),
}
)
login_url = auth_req.request(client.authorization_endpoint)
+1 -1
View File
@@ -1,2 +1,2 @@
Flask==3.0.3
oic==1.6.1
oic==1.7.0
+93
View File
@@ -0,0 +1,93 @@
# Principe de fonctionnement de la passerelle ProConnect vers RENATER (`OIDC2FER`)
## Vue d'ensemble
Ce premier schéma donne un aperçu des échanges entre :
* un Fournisseur de Service ProConnecté (ex. https://pad.numerique.gouv.fr) ;
* le service ProConnect (hébergé sur `https://auth.agentconnect.gouv.fr`) ;
* la passerelle `OIDC2FER` (hébergé sur `https://renater.agentconnect.gouv.fr`) ;
* le service de _discovery_ ou _Where Are You From (WAYF)_ RENATER qui permet à l'utilisateur de choisir son établissement de rattachement, hébergé sur https://discovery.renater.fr/agentconnect/ ;
* le serveur d'identité SAML de l'établissement sélectionné (par exemple, https://cas.inria.fr).
``` mermaid
sequenceDiagram
participant FS as Fournisseur<br>de Service
participant ProConnect
box LightYellow
participant OIDC2FER
end
participant WAYF as WAYF<br>RENATER
participant IdP as IdP SAML
FS->>ProConnect: OIDC auth request
ProConnect->>OIDC2FER: OIDC auth request
OIDC2FER->>WAYF: discovery request
WAYF->>OIDC2FER: IdP entityID
OIDC2FER->>IdP: SAML AuthnRequest
IdP->>OIDC2FER: SAML Assertion
OIDC2FER->>ProConnect: OIDC tokens+userinfo
ProConnect->>FS: OIDC tokens+userinfo
```
## Détail des échanges
Noter que le schéma précédent ne reflète pas le détail des échanges entre l'utilisateur, son navigateur (qu'il est utile de distinguer pour illustrer que plusieurs échanges se font sans intervention humaine), et les différents services mentionnés. En voici une version exhaustive :
``` mermaid
sequenceDiagram
actor Utilisateur
participant Navigateur
participant FS as Fournisseur<br>de Service
participant ProConnect
box LightYellow
participant OIDC2FER
end
participant WAYF as WAYF<br>RENATER
participant IdP SAML
Utilisateur->>Navigateur: Saisie/clic URL FS
Navigateur->>FS: Requête d'une page du FS
opt si pas de session FS ouverte
FS->>Utilisateur: Présentation page d'accueil avec bouton ProConnect
Utilisateur->>FS: Clic bouton ProConnect
FS->>Navigateur: Redirection OIDC vers ProConnect avec state, nonce
Navigateur->>ProConnect: Requête GET avec state, nonce
opt si pas de session ProConnect ouverte
ProConnect->>Utilisateur: Présentation mire ProConnect
Utilisateur->>ProConnect: Clic bouton RENATER, ou saisie "robert@univ-exemple.fr"
ProConnect->>Navigateur: Redirection OIDC vers OIDC2FER avec state, nonce
Navigateur->>OIDC2FER: Requête GET avec state, nonce
OIDC2FER->>Navigateur: Redirection vers WAYF
Navigateur->>WAYF: Requête GET
opt si pas de préselection enregistrée dans le WAYF
WAYF->>Utilisateur: Présentation liste d'établissements autorisés
Utilisateur->>WAYF: Choix d'un établissement
end
WAYF->>Navigateur: Redirection vers OIDC2FER avec entityID IdP
Navigateur->>OIDC2FER: Requête GET avec entityID IdP
OIDC2FER->>Navigateur: Redirection vers IdP avec AuthnRequest SAML dans l'URL
Navigateur->>IdP SAML: Requête GET avec AuthnRequest SAML dans l'URL
opt si pas de session IdP ouverte
IdP SAML->>Utilisateur: Présentation mire de connexion IdP
Utilisateur->>IdP SAML: Saisie identifiants de connexion
end
IdP SAML->>Navigateur: Page avec formulaire contenant assertion SAML
Navigateur->>OIDC2FER: Requête POST avec assertion SAML
Note over OIDC2FER: Validation<br>eduPersonAffiliation
Note over OIDC2FER: Conversion attributs<br>SAML vers OIDC
OIDC2FER->>Navigateur: Redirection vers callback OIDC ProConnect avec authorization_code
Navigateur->>ProConnect: Requête GET avec authorization_code
ProConnect->>OIDC2FER: Demande tokens avec authorization_code
OIDC2FER->>ProConnect: Tokens OIDC
ProConnect->>OIDC2FER: Demande userinfo avec accessToken
OIDC2FER->>ProConnect: userinfo
end
ProConnect->>Navigateur: Redirection vers callback OIDC FS avec authorization_code
Navigateur->>FS: Requête GET avec authorization_code
FS->>ProConnect: Demande tokens avec authorization_code
ProConnect->>FS: Tokens OIDC
FS->>ProConnect: Demande userinfo avec accessToken
ProConnect->>FS: userinfo
end
FS->>Utilisateur: Contenu du service
```
+1 -1
Submodule secrets updated: 647b88689c...d196a86833
@@ -0,0 +1,22 @@
image:
repository: localhost:5001/oidc-test-client
pullPolicy: Always
tag: "latest"
ingress:
enabled: true
host: oidc-test-client.127.0.0.1.nip.io
env:
- name: OIDC_ROOT_URL
value: https://oidc-test-client.127.0.0.1.nip.io
- name: OIDC_PROVIDER
value: https://oidc2fer.127.0.0.1.nip.io
- name: OIDC_CLIENT_ID
value: oidc-test-client
- name: OIDC_CLIENT_SECRET
value: oidc-test-secret
- name: OIDC_SCOPES
value: openid,uid,given_name,usual_name,email,siret
- name: EXTRA_ROOT_CA
value: {{ exec "mkcert" (list "-CAROOT") | trim | printf "%s/rootCA.pem" | readFile | quote }}
+46 -26
View File
@@ -5,37 +5,57 @@ image:
satosa:
replicas: 1
envVars:
BASE_URL: https://oidc2fer.127.0.0.1.nip.io
GUNICORN_CMD_ARGS: --workers=3
env:
- name: BASE_URL
value: https://oidc2fer.127.0.0.1.nip.io
- name: GUNICORN_CMD_ARGS
value: --workers=3
LOG_LEVEL: debug
- name: LOG_LEVEL
value: debug
SAML2_DISCOVERY_URL: https://discovery.renater.fr/test/
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/test/preview/preview-idps-test-metadata.xml
SAML2_ENTITY_ID: https://oidc2fer.127.0.0.1.nip.io/Saml2/proxy_saml2_backend.xml
- name: SAML2_DISCOVERY_URL
value: https://discovery.renater.fr/test/
- name: SAML2_METADATA_URL
value: https://pub.federation.renater.fr/metadata/test/idps.xml
- name: SAML2_ENTITY_ID
value: https://oidc2fer.127.0.0.1.nip.io/Saml2/proxy_saml2_backend.xml
STATE_ENCRYPTION_KEY: { secretKeyRef: { name: oidc2fer, key: STATE_ENCRYPTION_KEY } }
SAML2_BACKEND_CERT: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_CERT } }
SAML2_BACKEND_KEY: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_KEY } }
OIDC_FRONTEND_KEY: { secretKeyRef: { name: oidc2fer, key: OIDC_FRONTEND_KEY } }
- name: STATE_ENCRYPTION_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: STATE_ENCRYPTION_KEY } }
- name: SAML2_BACKEND_CERT
valueFrom: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_CERT } }
- name: SAML2_BACKEND_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_KEY } }
- name: OIDC_FRONTEND_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: OIDC_FRONTEND_KEY } }
OIDC_DB_URI: "redis://:pass@redis-master:6379"
CLIENT_DB_JSON: |-
{
"oidc-test-client": {
"response_types": [
"id_token",
"code"
],
"redirect_uris": [
"https://oidc-test-client.traefik.me/redirect_uri",
"https://oidc-test-client.traefik.me:8443/redirect_uri"
],
"client_secret": "oidc-test-secret",
"token_endpoint_auth_method": "client_secret_post"
- name: OIDC_DB_URI
value: "stateless://:$(STATE_ENCRYPTION_KEY)@localhost"
- name: CLIENT_DB_JSON
value: |-
{
"oidc-test-client": {
"response_types": [
"id_token",
"code"
],
"redirect_uris": [
"https://oidc-test-client.traefik.me/redirect_uri",
"https://oidc-test-client.traefik.me:8443/redirect_uri",
"https://oidc-test-client.127.0.0.1.nip.io/redirect_uri"
],
"client_secret": "oidc-test-secret",
"token_endpoint_auth_method": "client_secret_post"
}
}
- name: SIRET_MAP
value: |-
{
"https://test-idp.federation.renater.fr/idp/shibboleth": "12345678200010"
}
}
ingress:
enabled: true
@@ -1,27 +1,172 @@
image:
repository: lasuite/oidc2fer
repository: ghcr.io/proconnect-gouv/oidc2fer
pullPolicy: Always
tag: "v1.0.3"
tag: "v1.0.13"
satosa:
replicas: 2
envVars:
BASE_URL: https://renater.agentconnect.gouv.fr
GUNICORN_CMD_ARGS: --workers=3
env:
- name: BASE_URL
value: https://renater.agentconnect.gouv.fr
- name: GUNICORN_CMD_ARGS
value: --workers=3
LOG_LEVEL: info
- name: LOG_LEVEL
value: INFO
- name: LOG_LEVELS
value: '{ "satosa.backends.saml2": "DEBUG" }'
SAML2_DISCOVERY_URL: https://discovery.renater.fr/agentconnect/
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/renater/main/main-idps-renater-metadata.xml
SAML2_ENTITY_ID: https://renater.agentconnect.gouv.fr/Saml2/proxy_saml2_backend.xml
- name: SAML2_DISCOVERY_URL
value: https://discovery.renater.fr/agentconnect/
- name: SAML2_METADATA_URL
value: https://pub.federation.renater.fr/metadata/fer/idps.xml
- name: SAML2_ENTITY_ID
value: https://renater.agentconnect.gouv.fr/Saml2/proxy_saml2_backend.xml
STATE_ENCRYPTION_KEY: { secretKeyRef: { name: oidc2fer, key: STATE_ENCRYPTION_KEY } }
SAML2_BACKEND_CERT: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_CERT } }
SAML2_BACKEND_KEY: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_KEY } }
OIDC_FRONTEND_KEY: { secretKeyRef: { name: oidc2fer, key: OIDC_FRONTEND_KEY } }
CLIENT_DB_JSON: { secretKeyRef: { name: oidc2fer, key: CLIENT_DB_JSON } }
- name: STATE_ENCRYPTION_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: STATE_ENCRYPTION_KEY } }
- name: SAML2_BACKEND_CERT
valueFrom: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_CERT } }
- name: SAML2_BACKEND_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_KEY } }
- name: OIDC_FRONTEND_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: OIDC_FRONTEND_KEY } }
- name: CLIENT_DB_JSON
valueFrom: { secretKeyRef: { name: oidc2fer, key: CLIENT_DB_JSON } }
OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } }
- name: OIDC_DB_URI
value: "stateless://:$(STATE_ENCRYPTION_KEY)@localhost"
# As provided by RENATER
- name: SIRET_MAP
value: |-
{
"http://idp.univ-poitiers.fr": "19860856400375",
"https://apps.univ-lr.fr/idp/shibboleth": "19170032700015",
"https://auth.sso.vet-alfort.fr/saml/metadata": "19940608300014",
"https://auth.uttop.fr/saml/metadata": "19650048200019",
"https://authentification.inrae.fr/saml/metadata/idp": "18007003901803",
"https://federation.ens.psl.eu/idp/shibboleth": "19753459700012",
"https://federation.umontpellier.fr/idp/shibboleth": "13002979600013",
"https://federation.unimes.fr/idp/shibboleth": "93249157400012",
"https://federation.upf.pf/idp/shibboleth": "19987001500013",
"https://federation.utbm.fr/idp/shibboleth": "19900356700013",
"https://federation3.univ-brest.fr/idp/shibboleth": "94129831700012",
"https://idauthproxy.insa-lyon.fr/Saml2IDP/metadata": "19690192000013",
"https://ident-shib.ensc-rennes.fr/idp/shibboleth": "19350077400016",
"https://identification.univ-toulouse.fr": "13002132200016",
"https://identites.ec-lyon.fr/idp/shibboleth": "19690187000010",
"https://identites.ensea.fr/idp/shibboleth": "19951376300011",
"https://identities.univ-jfc.fr/idp/prod": "19811201300018",
"https://idp-genci.renater.fr/idp/shibboleth": "49468697500041",
"https://idp-ng.univ-st-etienne.fr/idp/shibboleth": "93850168100010",
"https://idp-septa.onisep.fr/idp/shibboleth": "18004302800653",
"https://idp-v4.univ-montp3.fr/idp/shibboleth": "93249089900014",
"https://idp.bnu.fr/idp/shibboleth": "18004406700015",
"https://idp.cirad.fr/idp/shibboleth": "33159627000016",
"https://idp.crous-reims.fr/idp/shibboleth": "18510200100327",
"https://idp.ec-nantes.fr/idp/shibboleth": "19440100600011",
"https://idp.ehesp.fr/idp/shibboleth": "13000362700010",
"https://idp.ens-lyon.fr/idp/shibboleth": "13000812100019",
"https://idp.ensam.eu/idp/shibboleth": "19753472000010",
"https://idp.ensfea.fr/idp/shibboleth": "19310143300012",
"https://idp.ensma.fr/idp/shibboleth": "19860073600021",
"https://idp.ent.dauphine.fr/idp/shibboleth": "19754692200018",
"https://idp.imt-atlantique.fr/idp/shibboleth": "18009202500121",
"https://idp.inha.fr/idp/shibboleth": "19754688000018",
"https://idp.inp-toulouse.fr/idp/shibboleth": "19311381800127",
"https://idp.insa-rennes.fr/idp/shibboleth": "19350097200016",
"https://idp.institut-agro.fr/idp": "13002622200013",
"https://idp.isae.fr/idp/shibboleth": "13000427800011",
"https://idp.lyon.archi.fr/idp/shibboleth": "19690184700018",
"https://idp.renater.fr/idp/shibboleth": "18008947600055",
"https://idp.rennes.archi.fr/idp/shibboleth": "19350089900029",
"https://idp.sciencespo-lyon.fr/idp/shibboleth": "19690173000024",
"https://idp.sciencespobordeaux.fr/idp/shibboleth": "19330192600039",
"https://idp.uca.fr/idp/shibboleth": "13002806100013",
"https://idp.uha.fr/idp/shibboleth": "19681166500013",
"https://idp.unistra.fr/idp/shibboleth": "13000545700010",
"https://idp.univ-avignon.fr/idp/shibboleth": "19840685200204",
"https://idp.univ-cotedazur.fr/idp/shibboleth": "13002566100013",
"https://idp.univ-eiffel.fr/idp/shibboleth": "13002612300013",
"https://idp.univ-lemans.fr/idp/shibboleth": "19720916600010",
"https://idp.univ-lille.fr/idp/shibboleth": "13002975400012",
"https://idp.univ-lyon2.fr/idp/shibboleth": "19691775100014",
"https://idp.univ-lyon3.fr/idp/shibboleth": "19692437700282",
"https://idp.univ-orleans.fr/idp/shibboleth": "19450855200016",
"https://idp.univ-perp.fr/idp/shibboleth": "19660437500010",
"https://idp.univ-tln.fr/idp/shibboleth": "19830766200017",
"https://idp.univ-tlse3.fr/idp/shibboleth": "93827139200012",
"https://idp.univ-tours.fr/idp/shibboleth": "19370800500478",
"https://idp.universite-paris-saclay.fr/idp": "13002602400054",
"https://idp1.agroparistech.fr/idp/shibboleth": "13000285000134",
"https://idp1.ens-paris-saclay.fr/idp/shibboleth": "19940607500036",
"https://idp1.univ-fcomte.fr/idp/shibboleth": "93810656400017",
"https://idp2.amue.fr/idp/shibboleth": "18004312700091",
"https://idp2.brgm.fr/idp/shibboleth": "58205614900120",
"https://idp2.emse.fr/idp/shibboleth": "18009202500105",
"https://idp2.univ-paris8.fr/idp/shibboleth": "19931827000014",
"https://idp3.univ-lorraine.fr/idp/shibboleth": "13001550600012",
"https://idp3.ut-capitole.fr/idp/shibboleth": "13003061200019",
"https://idp4.unicaen.fr/idp/shibboleth": "19141408500016",
"https://idpv3.univ-amu.fr/idp/shibboleth": "13001533200013",
"https://janus.cnrs.fr/idp": "18008901303720",
"https://multipass.imt-nord-europe.fr/idp/shibboleth": "18009202500139",
"https://papangue.vetagro-sup.fr/idp/shibboleth": "13000858400018",
"https://prod-idp.inria.fr": "18008904700013",
"https://ruhnu.univ-tlse2.fr/idp/shibboleth": "19311383400017",
"https://shib.mines-albi.fr/idp/shibboleth": "18009202500097",
"https://shib.univ-lehavre.fr/idp/shibboleth": "19762762300097",
"https://identite.univ-reims.fr/idp/shibboleth": "19511296600799",
"https://shib2.unc.nc/idp/shibboleth": "13000322100012",
"https://shibboleth.ens2m.fr/idp/shibboleth": "19250082500026",
"https://shibboleth.grenoble-inp.fr/idp/shibboleth": "19381912500017",
"https://shibboleth.insa-rouen.fr/idp/shibboleth": "19760165100023",
"https://shibboleth.ube.fr/idp/shibboleth": "93823061200013",
"https://shibboleth.univ-corse.fr/idp/shibboleth": "19202664900264",
"https://shibboleth.univ-evry.fr/idp/shibboleth": "19911975100014",
"https://shibboleth.univ-grenoble-alpes.fr/idp/shibboleth": "13002608100013",
"https://shibboleth.univ-savoie.fr/idp/shibboleth": "19730858800015",
"https://shibboleth03.chartes.psl.eu/idp/shibboleth": "19753478700043",
"https://shibboleth3.utt.fr/idp/shibboleth": "19101060200032",
"https://srv-fii.insa-toulouse.fr/idp/shibboleth": "19310152400018",
"https://sso-ciation-saclay.centralesupelec.fr/idp/shibboleth": "13002076100016",
"https://sso.bordeaux-inp.fr/idp/shibboleth": "13000635600013",
"https://sso.ird.fr/idp/shibboleth": "18000602500159",
"https://upnidp2.parisnanterre.fr/idp/shibboleth": "19921204400010",
"https://vip.espci.fr/saml2/idp/metadata.php": "20000068500012",
"urn:mace:cru.fr:federation:uhb.fr": "19350937900015",
"urn:mace:cru.fr:federation:unilim.fr": "19870669900321",
"urn:mace:cru.fr:federation:univ-nantes.fr": "13002974700016",
"urn:mace:cru.fr:federation:univ-paris1.fr": "19751717000019",
"urn:mace:cru.fr:federation:univ-rennes1.fr": "13003051300019",
"urn:mace:cru.fr:federation:univ-rouen.fr": "19761904200017",
"urn:mace:cru.fr:federation:univ-ubs.fr": "19561718800600",
"https://idp.marseille.archi.fr/idp/shibboleth": "19130236300020",
"https://idp.inalco.fr/idp/shibboleth": "19753488600092",
"https://idp.sigma-clermont.fr/idp/shibboleth": "13002191800011",
"https://idp.univ-artois.fr/idp/shibboleth": "19624401600016",
"https://idp2.ensai.fr/idp/shibboleth": "13001422800055",
"urn:mace:cru.fr:federation:univ-lyon1.fr": "19691774400019",
"https://idp.enitab.fr/idp/shibboleth": "19330203100011",
"https://idp.oca.eu/idp/shibboleth": "19061563300012",
"https://idp.univ-paris13.fr/idp/shibboleth": "19931238000017",
"https://idp.ens-rennes.fr/idp/shibboleth": "13001848400019",
"https://idp.uphf.fr/idp/shibboleth": "13002574500014",
"https://idp.college-de-france.fr/idp/shibboleth": "19753480300014",
"https://shibboleth.csi.uvsq.fr/idp/shibboleth": "19781944400013",
"https://idp.esaip.org/idp/shibboleth": "37920438100014",
"https://idp.eskn.fr/idp/shibboleth": "13002617200010",
"https://auth.centrale-marseille.fr/idp/shibboleth": "19133340000015",
"https://idp-insa-cvl.renater.fr/idp/shibboleth": "13001833600011",
"https://shibboleth3.obspm.fr/idp/shibboleth": "19753496900013",
"https://idp-ecole-air.renater.fr/idp/shibboleth": "13002454000010",
"https://idp.enpc.fr/saml/metadata": "19753501600020",
"https://idp.paris-malaquais.archi.fr/idp/shibboleth": "18009221500011",
"https://idp.univ-pau.fr/idp/shibboleth": "19640251500270",
"https://idp4.cereq.fr/idp/shibboleth": "18004303600037",
"urn:mace:cru.fr:federation:univ-angers.fr": "19490970100303"
}
ingress:
enabled: true
@@ -1,32 +1,52 @@
image:
repository: lasuite/oidc2fer
repository: ghcr.io/proconnect-gouv/oidc2fer
pullPolicy: Always
tag: "main"
tag: "latest"
satosa:
replicas: 2
envVars:
BASE_URL: https://oidc2fer-staging.beta.numerique.gouv.fr
GUNICORN_CMD_ARGS: --workers=3
env:
- name: BASE_URL
value: https://oidc2fer-staging.beta.numerique.gouv.fr
- name: GUNICORN_CMD_ARGS
value: --workers=3
LOG_LEVEL: debug
- name: LOG_LEVEL
value: DEBUG
- name: LOG_LEVELS
value: '{ "satosa.backends.saml2": "DEBUG" }'
SAML2_DISCOVERY_URL: https://discovery.renater.fr/test/
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/test/preview/preview-idps-test-metadata.xml
SAML2_ENTITY_ID: https://oidc2fer-staging.beta.numerique.gouv.fr/Saml2/proxy_saml2_backend.xml-test
- name: SAML2_DISCOVERY_URL
value: https://discovery.renater.fr/test/
- name: SAML2_METADATA_URL
value: https://pub.federation.renater.fr/metadata/test/idps.xml
- name: SAML2_ENTITY_ID
value: https://oidc2fer-staging.beta.numerique.gouv.fr/Saml2/proxy_saml2_backend.xml-test
STATE_ENCRYPTION_KEY: { secretKeyRef: { name: oidc2fer, key: STATE_ENCRYPTION_KEY } }
SAML2_BACKEND_CERT: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_CERT } }
SAML2_BACKEND_KEY: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_KEY } }
OIDC_FRONTEND_KEY: { secretKeyRef: { name: oidc2fer, key: OIDC_FRONTEND_KEY } }
CLIENT_DB_JSON: { secretKeyRef: { name: oidc2fer, key: CLIENT_DB_JSON } }
- name: STATE_ENCRYPTION_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: STATE_ENCRYPTION_KEY } }
- name: SAML2_BACKEND_CERT
valueFrom: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_CERT } }
- name: SAML2_BACKEND_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: SAML2_BACKEND_KEY } }
- name: OIDC_FRONTEND_KEY
valueFrom: { secretKeyRef: { name: oidc2fer, key: OIDC_FRONTEND_KEY } }
- name: CLIENT_DB_JSON
valueFrom: { secretKeyRef: { name: oidc2fer, key: CLIENT_DB_JSON } }
OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } }
- name: OIDC_DB_URI
value: "stateless://:$(STATE_ENCRYPTION_KEY)@localhost"
- name: SIRET_MAP
value: |-
{
"https://test-idp.federation.renater.fr/idp/shibboleth": "12345678200010"
}
ingress:
enabled: true
host: oidc2fer-staging.beta.numerique.gouv.fr
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
-5
View File
@@ -1,5 +0,0 @@
apiVersion: v2
name: extra
description: A Helm chart to add some manifests to oidc2fer
type: application
version: 0.1.0
-7
View File
@@ -1,7 +0,0 @@
apiVersion: core.libre.sh/v1alpha1
kind: Redis
metadata:
name: redis
namespace: {{ .Release.Namespace | quote }}
spec:
disableAuth: false
@@ -1,35 +1,3 @@
repositories:
- name: bitnami
url: registry-1.docker.io/bitnamicharts
oci: true
releases:
- name: redis
installed: {{ eq .Environment.Name "dev" | toYaml }}
namespace: {{ .Namespace }}
chart: bitnami/redis
version: 18.19.2
values:
- auth:
password: pass
architecture: standalone
- name: extra
installed: {{ ne .Environment.Name "dev" | toYaml }}
namespace: {{ .Namespace }}
chart: ./extra
secrets:
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
- name: oidc2fer
version: {{ .Values.version }}
namespace: {{ .Namespace }}
chart: ./oidc2fer
values:
- env.d/{{ .Environment.Name }}/values.oidc2fer.yaml.gotmpl
secrets:
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
environments:
dev:
values:
@@ -46,4 +14,26 @@ environments:
- version: 0.0.1
secrets:
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
---
repositories:
- name: bitnami
url: registry-1.docker.io/bitnamicharts
oci: true
releases:
- name: oidc2fer
version: {{ .Values.version }}
namespace: {{ .Namespace }}
chart: ./oidc2fer
values:
- env.d/{{ .Environment.Name }}/values.oidc2fer.yaml.gotmpl
secrets:
- env.d/{{ .Environment.Name }}/secrets.enc.yaml
- name: oidc-test-client
installed: {{ eq .Environment.Name "dev" }}
version: {{ .Values.version }}
namespace: {{ .Namespace }}
chart: ./oidc-test-client
values:
- env.d/{{ .Environment.Name }}/values.oidc-test-client.yaml.gotmpl
+24
View File
@@ -0,0 +1,24 @@
apiVersion: v2
name: oidc-test-client
description: Test OIDC client
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.0.0"
@@ -0,0 +1,51 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "oidc-test-client.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "oidc-test-client.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "oidc-test-client.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "oidc-test-client.labels" -}}
helm.sh/chart: {{ include "oidc-test-client.chart" . }}
{{ include "oidc-test-client.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "oidc-test-client.selectorLabels" -}}
app.kubernetes.io/name: {{ include "oidc-test-client.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "oidc-test-client.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "oidc-test-client.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "oidc-test-client.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "oidc-test-client.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
{{ with .Values.livenessProbe }}
livenessProbe: {{ toJson . }}
{{- end }}
{{ with .Values.readinessProbe }}
readinessProbe: {{ toJson . }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
command:
- sh
- -ec
- |-
printenv EXTRA_ROOT_CA >> $(python -m certifi)
exec python app.py
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,38 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "oidc-test-client.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "oidc-test-client.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.className }}
{{- if .Values.ingress.tls.enabled }}
tls:
{{- if .Values.ingress.host }}
- secretName: {{ $fullName }}-tls
hosts:
- {{ .Values.ingress.host | quote }}
{{- end }}
{{- end }}
rules:
{{- if .Values.ingress.host }}
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "oidc-test-client.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "oidc-test-client.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "oidc-test-client.selectorLabels" . | nindent 4 }}
+71
View File
@@ -0,0 +1,71 @@
# Default values for oidc-test-client.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: oidc-test-client
pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
podAnnotations: {}
podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 5000
ingress:
enabled: true
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts: []
tls:
enabled: true
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 30
readinessProbe: null
nodeSelector: {}
tolerations: []
affinity: {}
env: []
-40
View File
@@ -50,46 +50,6 @@ app.kubernetes.io/name: {{ include "oidc2fer.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
transform dictionnary of environment variables
Usage : {{ include "oidc2fer.env.transformDict" .Values.envVars }}
Example:
envVars:
# Using simple strings as env vars
ENV_VAR_NAME: "envVar value"
# Using a value from a configMap
ENV_VAR_FROM_CM:
configMapKeyRef:
name: cm-name
key: "key_in_cm"
# Using a value from a secret
ENV_VAR_FROM_SECRET:
secretKeyRef:
name: secret-name
key: "key_in_secret"
*/}}
{{- define "oidc2fer.env.transformDict" -}}
{{- range $key, $value := . }}
- name: {{ $key | quote }}
{{- if $value | kindIs "map" }}
valueFrom: {{ $value | toYaml | nindent 4 }}
{{- else }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{/*
oidc2fer env vars
*/}}
{{- define "oidc2fer.common.env" -}}
{{- $topLevelScope := index . 0 -}}
{{- $workerScope := index . 1 -}}
{{- include "oidc2fer.env.transformDict" $workerScope.envVars -}}
{{- end }}
{{/*
Common labels
+7 -4
View File
@@ -1,5 +1,6 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "oidc2fer.fullname" . -}}
{{- $port := .Values.satosa.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
@@ -33,13 +34,15 @@ spec:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /
pathType: Prefix
{{- range .Values.ingress.paths }}
- path: {{ .path | quote }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "oidc2fer.satosa.fullname" . }}
name: {{ $fullName }}-satosa
port:
number: {{ .Values.satosa.service.port }}
number: {{ $port }}
{{- end }}
{{- with .Values.ingress.customBackends }}
{{- toYaml . | nindent 10 }}
{{- end }}
@@ -1,4 +1,3 @@
{{- $envVars := include "oidc2fer.common.env" (list . .Values.satosa) -}}
{{- $fullName := include "oidc2fer.satosa.fullname" . -}}
{{- $component := "satosa" -}}
apiVersion: apps/v1
@@ -42,10 +41,9 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- with .Values.satosa.env }}
env: {{ toJson . }}
{{- end }}
{{- with .Values.satosa.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -82,3 +80,16 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
---
{{ if .Values.satosa.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace | quote }}
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "oidc2fer.common.selectorLabels" (list . $component) | nindent 6 }}
{{ end }}
@@ -1,4 +1,3 @@
{{- $envVars := include "oidc2fer.common.env" (list . .Values.satosa) -}}
{{- $fullName := include "oidc2fer.satosa.fullname" . -}}
{{- $component := "satosa" -}}
apiVersion: v1
+12 -14
View File
@@ -21,10 +21,6 @@ image:
nameOverride: ""
fullnameOverride: ""
## @skip commonEnvVars
commonEnvVars: &commonEnvVars
<<: []
## @param ingress.enabled whether to enable the Ingress or not
## @param ingress.className IngressClass to use for the Ingress
## @param ingress.host Host for the Ingress
@@ -33,7 +29,9 @@ ingress:
enabled: false
className: null
host: oidc2fer.example.com
path: /
paths:
- path: '/$|^/\.well-known/openid-configuration$|^/images/|^/Saml2/|^/OIDC/|^/ping$'
pathType: ImplementationSpecific # enables regex matching
## @param ingress.hosts Additional host to configure for the Ingress
hosts: []
# - chart-example.local
@@ -48,10 +46,17 @@ ingress:
## @param ingress.customBackends Add custom backends to ingress
customBackends: []
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
## @section satosa
satosa:
## @param satosa.pdb.enabled Enable pdb on backend
pdb:
enabled: true
## @param satosa.command Override the satosa container command
command: []
@@ -70,15 +75,8 @@ satosa:
## @param satosa.securityContext Configure satosa Pod security context
securityContext: null
## @param satosa.envVars Configure satosa container environment variables
## @extra satosa.envVars.BY_VALUE Example environment variable by setting value directly
## @extra satosa.envVars.FROM_CONFIGMAP.configMapKeyRef.name Name of a ConfigMap when configuring env vars from a ConfigMap
## @extra satosa.envVars.FROM_CONFIGMAP.configMapKeyRef.key Key within a ConfigMap when configuring env vars from a ConfigMap
## @extra satosa.envVars.FROM_SECRET.secretKeyRef.name Name of a Secret when configuring env vars from a Secret
## @extra satosa.envVars.FROM_SECRET.secretKeyRef.key Key within a Secret when configuring env vars from a Secret
## @skip satosa.envVars
envVars:
<<: *commonEnvVars
## @param satosa.env Configure satosa container environment variables
env: []
## @param satosa.podAnnotations Annotations to add to the satosa Pod
podAnnotations: {}
+3
View File
@@ -31,3 +31,6 @@ attributes:
uid:
openid:
- uid
siret:
openid:
- siret
@@ -0,0 +1 @@
from .entity_id_to_siret_mapper import EntityIdToSiretMapper
@@ -0,0 +1,23 @@
import json
import logging
from satosa.micro_services.base import ResponseMicroService
logger = logging.getLogger(__name__)
class EntityIdToSiretMapper(ResponseMicroService):
def __init__(self, config, *args, **kwargs):
super().__init__(*args, **kwargs)
self.attribute = config.get("attribute", "siret")
self.mapping = json.loads(config.get("mapping_json", "{}"))
def process(self, context, data):
entity_id = data.auth_info.issuer
if entity_id in self.mapping:
siret = self.mapping[entity_id]
logger.info("Mapping entity ID %s to SIRET %s", entity_id, siret)
data.attributes[self.attribute] = siret
else:
logger.warning("No SIRET mapping found for entity ID %s", entity_id)
return super().process(context, data)
@@ -3,11 +3,11 @@ name: Saml2
config:
disco_srv: !ENV SAML2_DISCOVERY_URL
entityid_endpoint: true
mirror_force_authn: 'no'
memorize_idp: 'no'
use_memorized_idp_when_force_authn: 'no'
send_requester_id: 'no'
enable_metadata_reload: 'no'
mirror_force_authn: false
memorize_idp: false
use_memorized_idp_when_force_authn: false
send_requester_id: false
enable_metadata_reload: false
acs_selection_strategy: prefer_matching_host
sp_config:
name: Passerelle OIDC vers FER
@@ -43,6 +43,7 @@ config:
- uid
- given_name
- usual_name
- siret
# Set code/token lifetimes as short as possible
authorization_code_lifetime: 60
@@ -56,8 +57,5 @@ config:
- given_name
usual_name:
- usual_name
extra_id_token_claims:
oidc-test-client:
- acr
agent-connect:
- acr
siret:
- siret
@@ -13,4 +13,8 @@ config:
# later in the PrimaryIdentifier processor.
- ".+"
eduPersonAffiliation:
- "^faculty$"
- "^staff$"
- "^employee$"
- "^researcher$"
- "^teacher$"
@@ -0,0 +1,5 @@
module: oidc2fer.attribute_generators.EntityIdToSiretMapper
name: EntityIdToSiretMapper
config:
attribute: siret
mapping_json: !ENV SIRET_MAP
+1
View File
@@ -17,6 +17,7 @@ MICRO_SERVICES:
- plugins/microservices/primary_identifier.yaml
- plugins/microservices/static_attributes.yaml
- plugins/microservices/attribute_processor.yaml
- plugins/microservices/siret_mapping.yaml
LOGGING:
# All the logging configuration is done in the Gunicorn config, this is just
# here to avoid overwriting it with the default SATOSA config
+16 -19
View File
@@ -11,47 +11,44 @@ version = "0.1.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.14",
]
description = "An application to handle contacts and teams."
description = "A gateway from RENATER's FER to ProConnect's OIDC provider, built on top of SATOSA."
keywords = ["OIDC", "SAML", "Shibboleth", "FER", "RENATER"]
license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.11"
requires-python = ">=3.14"
dependencies = [
"SATOSA==8.4.0",
"gunicorn==22.0.0",
"redis==5.0.4",
"JSON-log-formatter==1.0",
"WhiteNoise==6.7.0",
"SATOSA==8.5.1",
"gunicorn==25.1.0",
"JSON-log-formatter==1.1.1",
"WhiteNoise==6.12.0",
]
[project.urls]
"Bug Tracker" = "https://github.com/numerique-gouv/oidc2fer/issues/new"
"Changelog" = "https://github.com/numerique-gouv/oidc2fer/blob/main/CHANGELOG.md"
"Homepage" = "https://github.com/numerique-gouv/oidc2fer"
"Repository" = "https://github.com/numerique-gouv/oidc2fer"
"Bug Tracker" = "https://github.com/proconnect-gouv/oidc2fer/issues/new"
"Changelog" = "https://github.com/proconnect-gouv/oidc2fer/blob/main/CHANGELOG.md"
"Homepage" = "https://github.com/proconnect-gouv/oidc2fer"
"Repository" = "https://github.com/proconnect-gouv/oidc2fer"
[project.optional-dependencies]
dev = [
"pylint==3.1.0",
"pytest-cov==4.1.0",
"pytest==8.0.2",
"ruff==0.2.2",
"pytest-playwright==0.4.4",
"pytest==9.0.2",
"ruff==0.15.4",
"pytest-playwright==0.7.2",
]
[tool.setuptools]
packages = { find = { where = ["."], exclude = ["tests"] } }
zip-safe = true
[tool.distutils.bdist_wheel]
universal = true
[tool.ruff]
exclude = [
".git",
-7
View File
@@ -1,7 +0,0 @@
#!/usr/bin/env python
"""Setup file for the oidc2fer module. All configuration stands in the setup.cfg file."""
# coding: utf-8
from setuptools import setup
setup()
@@ -0,0 +1,42 @@
import json
from satosa.context import Context
from satosa.internal import AuthenticationInformation, InternalData
from oidc2fer.attribute_generators import EntityIdToSiretMapper
class TestEntityIdToSiretMapper:
def create_mapper(self):
mapper = EntityIdToSiretMapper(
config={
"attribute": "siret",
"mapping_json": json.dumps(
{
"https://idp.example.fr": "12345678200010",
}
),
},
name="siret_mapper",
base_url="https://satosa.example.com",
)
mapper.next = lambda ctx, data: data
return mapper
def test_sets_siret_for_known_entityid(self):
mapper = self.create_mapper()
resp = InternalData(
auth_info=AuthenticationInformation(issuer="https://idp.example.fr")
)
ctx = Context()
mapper.process(ctx, resp)
assert resp.attributes["siret"] == "12345678200010"
def test_does_not_set_siret_for_unknown_entityid(self):
mapper = self.create_mapper()
resp = InternalData(
auth_info=AuthenticationInformation(issuer="https://unknown.example.fr")
)
ctx = Context()
mapper.process(ctx, resp)
assert "siret" not in resp.attributes
+62 -19
View File
@@ -1,5 +1,9 @@
import concurrent.futures
import json
import logging
import os
import time
import urllib.request
import pytest
from playwright.sync_api import Browser, Page, expect
@@ -20,6 +24,7 @@ def renater_test_idp(page, login):
def renater_wayf(page):
page.get_by_text("Veuillez sélectionner").click()
page.get_by_role("searchbox").fill("GIP RENATER - IdP de test")
page.get_by_role("option", name="GIP RENATER - IdP de test", exact=True).click()
page.get_by_role("button", name="Sélection").click()
@@ -31,7 +36,7 @@ def oidc_to_renater(
expected_given_name="Georges",
expected_usual_name="Grospieds",
):
page.goto("https://oidc-test-client.traefik.me")
page.goto("https://oidc-test-client.127.0.0.1.nip.io")
renater_wayf(page)
renater_test_idp(page, login=login)
@@ -40,6 +45,7 @@ def oidc_to_renater(
result = json.loads(text)
id_token = result["access_token_response"]["id_token"]
access_token = result["access_token_response"]["access_token"]
assert {"acr": "eidas1"}.items() <= id_token.items()
userinfo = result["userinfo"]
assert {
@@ -48,8 +54,9 @@ def oidc_to_renater(
"email": expected_email,
"given_name": expected_given_name,
"usual_name": expected_usual_name,
"siret": "12345678200010",
}.items() <= userinfo.items()
return id_token
return id_token, access_token
@pytest.mark.skipif(
@@ -57,80 +64,116 @@ def oidc_to_renater(
)
def test_oidc_to_renater_keeps_sub(browser: Browser):
with browser.new_context().new_page() as page:
id_token1 = oidc_to_renater(page)
id_token1, _ = oidc_to_renater(page)
with browser.new_context().new_page() as page:
id_token2 = oidc_to_renater(page)
id_token2, _ = oidc_to_renater(page)
assert id_token1["sub"] == id_token2["sub"]
@pytest.mark.skipif(
"BENCH_E2E" not in os.environ, reason="Benchmark runs only if requested"
)
def test_benchmark_userinfo(browser: Browser):
with browser.new_context().new_page() as page:
_, access_token = oidc_to_renater(page)
request_count = int(os.environ.get("BENCH_E2E_N", "100"))
client_count = int(os.environ.get("BENCH_E2E_P", "2"))
start = time.time()
def make_request():
return urllib.request.urlopen(
urllib.request.Request(
"https://oidc2fer.127.0.0.1.nip.io/OIDC/userinfo",
headers={"Authorization": f"Bearer {access_token}"},
)
)
with concurrent.futures.ThreadPoolExecutor(max_workers=client_count) as executor:
futures = []
for _ in range(request_count):
futures.append(executor.submit(make_request))
for future in concurrent.futures.as_completed(futures):
with future.result() as resp:
assert resp.status == 200
end = time.time()
logging.info(
"Average userinfo request time with %d parallel requests: \
%.2f seconds",
client_count,
(end - start) / request_count,
)
@pytest.mark.skipif(
"TEST_E2E" not in os.environ, reason="Depends on app running locally"
)
def test_oidc_to_renater_student_not_allowed(page: Page):
page.goto("https://oidc-test-client.traefik.me")
page.goto("https://oidc-test-client.127.0.0.1.nip.io")
renater_wayf(page)
renater_test_idp(page, login="etudiant1")
expect(page.locator("pre")).to_contain_text('"error":"access_denied"')
def agent_connect_login(page: Page, email):
def pro_connect_login(page: Page, email):
page.goto("https://fsa1v2.integ01.dev-agentconnect.fr/")
page.get_by_label("Connexion à AgentConnect").click()
page.get_by_role("button", name="Sidentifier avec ProConnect").click()
page.get_by_label("Email professionnel").fill(email)
page.get_by_test_id("interaction-connection-button").click()
def agent_connect_to_renater(
def pro_connect_to_renater(
page: Page,
login="enseignant1",
expected_email="georges.grospieds@formation.renater.fr",
expected_given_name="Georges",
expected_usual_name="Grospieds",
):
agent_connect_login(page, email=expected_email)
pro_connect_login(page, email=expected_email)
renater_wayf(page)
renater_test_idp(page, login=login)
expect(page.locator("body")).to_contain_text(expected_email)
text = page.inner_text("#json")
text = page.inner_text("#userinfo")
result = json.loads(text)
assert {
"uid": f"{login}@test-renater.fr",
"email": expected_email,
"given_name": expected_given_name,
"usual_name": expected_usual_name,
"siret": "12345678200010",
}.items() <= result.items()
return result
@pytest.mark.skipif(
"TEST_E2E_AC" not in os.environ, reason="Depends on staging deployment"
"TEST_E2E_PC" not in os.environ, reason="Depends on staging deployment"
)
def test_agent_connect_to_renater_keeps_sub(browser: Browser):
def test_pro_connect_to_renater_keeps_sub(browser: Browser):
with browser.new_context().new_page() as page:
result1 = agent_connect_to_renater(page)
result1 = pro_connect_to_renater(page)
with browser.new_context().new_page() as page:
result2 = agent_connect_to_renater(page)
result2 = pro_connect_to_renater(page)
assert result1["sub"] == result2["sub"]
@pytest.mark.skipif(
"TEST_E2E_AC" not in os.environ, reason="Depends on staging deployment"
"TEST_E2E_PC" not in os.environ, reason="Depends on staging deployment"
)
def test_agent_connect_to_renater_student_not_allowed(page: Page):
agent_connect_login(page, email="jean.dupont@formation.renater.fr")
def test_pro_connect_to_renater_student_not_allowed(page: Page):
pro_connect_login(page, email="jean.dupont@formation.renater.fr")
renater_wayf(page)
renater_test_idp(page, login="etudiant1")
expect(page.locator("body")).to_contain_text("Une erreur technique est survenue.")
expect(page.locator("body")).to_contain_text("access_denied")
@pytest.mark.skipif(
"TEST_E2E" not in os.environ, reason="Depends on app running locally"
)
def test_serve_static_assets(page: Page):
page.goto("https://satosa.traefik.me/images/logo.svg")
page.goto("https://oidc2fer.127.0.0.1.nip.io/images/logo.svg")
expect(page.locator("svg")).to_be_visible()
+14 -5
View File
@@ -26,7 +26,8 @@ fi
# https://github.com/kubernetes-sigs/kind/issues/2875
# https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration
# See: https://github.com/containerd/containerd/blob/main/docs/hosts.md
cat <<EOF | kind create cluster --config=-
kind --name=oidc2fer delete cluster || true
cat <<EOF | kind --name=oidc2fer create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
@@ -35,7 +36,6 @@ containerdConfigPatches:
config_path = "/etc/containerd/certs.d"
nodes:
- role: control-plane
image: kindest/node:v1.27.3
kubeadmConfigPatches:
- |
kind: InitConfiguration
@@ -50,9 +50,7 @@ nodes:
hostPort: 443
protocol: TCP
- role: worker
image: kindest/node:v1.27.3
- role: worker
image: kindest/node:v1.27.3
EOF
# 3. Add the registry config to the nodes
@@ -64,7 +62,7 @@ EOF
# We want a consistent name that works from both ends, so we tell containerd to
# alias localhost:${reg_port} to the registry container when pulling images
REGISTRY_DIR="/etc/containerd/certs.d/localhost:${reg_port}"
for node in $(kind get nodes); do
for node in $(kind --name=oidc2fer get nodes); do
docker exec "${node}" mkdir -p "${REGISTRY_DIR}"
cat <<EOF | docker exec -i "${node}" cp /dev/stdin "${REGISTRY_DIR}/hosts.toml"
[host."http://${reg_name}:5000"]
@@ -91,6 +89,17 @@ data:
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
# Patch the CoreDNS configmap to rewrite requests for *.127.0.0.1.nip.io to the Ingress Controller service
kubectl -n kube-system get configmap coredns -o yaml |
yq '.data.Corefile |= (
trim
| split("\n")
| .[-1] = " rewrite name regex .*\\.127\\.0\\.0\\.1\\.nip\\.io ingress-nginx-controller.ingress-nginx.svc.cluster.local answer auto\n"
+ .[-1]
| join("\n")
)' |
kubectl replace -f -
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl -n ingress-nginx create secret tls mkcert --key /tmp/127.0.0.1.nip.io+1-key.pem --cert /tmp/127.0.0.1.nip.io+1.pem
kubectl -n ingress-nginx patch deployments.apps ingress-nginx-controller --type 'json' -p '[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value":"--default-ssl-certificate=ingress-nginx/mkcert"}]'