Compare commits

..

1 Commits

Author SHA1 Message Date
Jonathan Perret 8f9a9ed1f0 temporary enable debug logs in production 2025-03-05 17:32:56 +01:00
49 changed files with 391 additions and 1023 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/proconnect-gouv/oidc2fer).
Please make sure you have read our [main Readme](https://github.com/numerique-gouv/oidc2fer).
Also make sure it was not already answered in [an open or close issue](https://github.com/proconnect-gouv/oidc2fer/issues).
Also make sure it was not already answered in [an open or close issue](https://github.com/numerique-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
@@ -1,67 +0,0 @@
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 }}"
+93
View File
@@ -0,0 +1,93 @@
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
+22
View File
@@ -0,0 +1,22 @@
name: Helmfile lint
run-name: Helmfile lint
on:
pull_request:
branches:
- 'main'
jobs:
helmfile-lint:
runs-on: ubuntu-latest
container:
image: ghcr.io/helmfile/helmfile:latest
steps:
-
uses: numerique-gouv/action-helmfile-lint@main
with:
app-id: ${{ secrets.APP_ID }}
age-key: ${{ secrets.SOPS_PRIVATE }}
private-key: ${{ secrets.PRIVATE_KEY }}
helmfile-src: "src/helm"
repositories: "oidc2fer,secrets"
@@ -1,4 +1,4 @@
name: 💚 CI Tests
name: OIDC2FER Workflow
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
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).github/**' | grep "print("
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- . ':(exclude)**/oidc2fer.yml' | 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check that the CHANGELOG has been modified in the current branch
run: git log --name-only --pretty="" origin/${{ github.event.pull_request.base.ref }}..HEAD | grep CHANGELOG
run: git whatchanged --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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
uses: actions/setup-python@v3
with:
python-version: '3.14'
python-version: '3.11'
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
uses: actions/setup-python@v3
with:
python-version: '3.14'
python-version: '3.11'
- name: Install development dependencies
run: |
# Python's xmlsec requirement
@@ -101,45 +101,3 @@ 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
+1 -1
View File
@@ -1,3 +1,3 @@
[submodule "secrets"]
path = secrets
url = ../../numerique-gouv/secrets
url = ../secrets
-33
View File
@@ -6,39 +6,6 @@ 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
@@ -1,32 +0,0 @@
# 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
```
+13 -16
View File
@@ -1,23 +1,25 @@
# ---- base image to inherit from ----
FROM python:3.14.3-slim-trixie AS common
FROM python:3.11-slim-bookworm as common
# Install xmlsec1 dependencies required for xmlsec (for SAML)
# Needs to be kept before the `pip install`
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
RUN apt-get update && \
apt-get -y upgrade && \
apt-get install -qy --no-install-recommends xmlsec1 && \
apt-get install -y \
pkg-config \
gcc \
xmlsec1 \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl && \
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
@@ -39,11 +41,7 @@ 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
# Install curl (for healthchecks)
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && apt-get install -qy curl
FROM common as development
# Playwright browsers
ENV PLAYWRIGHT_BROWSERS_PATH=/pw-browsers
@@ -69,15 +67,14 @@ 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
# Uninstall pip, setuptools and wheel after installation to reduce attack surface
RUN pip install . && pip uninstall -y setuptools wheel pip
RUN pip install .
# Switch to unprivileged user
USER ${DOCKER_USER}
+1 -8
View File
@@ -57,19 +57,12 @@ 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://github.com/proconnect-gouv/oidc2fer/pkgs/container/oidc2fer
https://hub.docker.com/r/lasuite/oidc2fer/tags .
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,11 +13,6 @@ 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 .'))
+9 -11
View File
@@ -13,8 +13,8 @@ services:
BASE_URL: https://satosa.traefik.me
GUNICORN_CMD_ARGS: --workers=2
TEST_E2E: 1
#TEST_E2E_PC: 1
OIDC_DB_URI: stateless://:STATE-ENCRYPTION-KEY@localhost
#TEST_E2E_AC: 1
OIDC_DB_URI: redis://redis/0
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/idps.xml
SAML2_METADATA_URL: https://pub.federation.renater.fr/metadata/test/preview/preview-idps-test-metadata.xml
SAML2_ENTITY_ID: https://satosa.traefik.me/Saml2/proxy_saml2_backend.xml
env_file:
- env.d/development/common
@@ -38,13 +38,8 @@ services:
volumes:
- ./src/satosa:/app
- ./docker/files/usr/local/etc/gunicorn/satosa.py:/usr/local/etc/gunicorn/satosa.py
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost:8000/ping"]
interval: 60s
timeout: 5s
retries: 3
start_period: 30s
start_interval: 1s
depends_on:
- redis
oidc-test-client:
build: docker/oidc-test-client
@@ -71,10 +66,13 @@ 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,siret
OIDC_SCOPES: openid,uid,given_name,usual_name,email
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,5 +39,11 @@ 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 "$@"
+1 -36
View File
@@ -1,5 +1,4 @@
import datetime
import json
import logging
import os
import sys
@@ -30,8 +29,6 @@ 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,
@@ -40,7 +37,7 @@ class RequestJSONFormatter(json_log_formatter.JSONFormatter):
"user_agent": record.args["a"],
"referer": record.args["f"],
"duration_in_ms": record.args["M"],
"pid": record.process,
"poppid": record.process,
}
@@ -58,23 +55,11 @@ 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"
@@ -91,9 +76,6 @@ 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,
@@ -113,24 +95,7 @@ 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.14.3-slim-trixie
FROM python:3.11
COPY . /app
+4 -2
View File
@@ -2,10 +2,13 @@ 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 Claims, ClaimsRequest, RegistrationResponse
from oic.oic.message import 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
@@ -49,7 +52,6 @@ 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.7.0
oic==1.6.1
+1 -1
Submodule secrets updated: d196a86833...b0d485544d
@@ -1,22 +0,0 @@
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 }}
+26 -46
View File
@@ -5,57 +5,37 @@ image:
satosa:
replicas: 1
env:
- name: BASE_URL
value: https://oidc2fer.127.0.0.1.nip.io
- name: GUNICORN_CMD_ARGS
value: --workers=3
envVars:
BASE_URL: https://oidc2fer.127.0.0.1.nip.io
GUNICORN_CMD_ARGS: --workers=3
- name: LOG_LEVEL
value: debug
LOG_LEVEL: debug
- 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
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: 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 } }
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: 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"
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"
}
}
ingress:
enabled: true
@@ -1,172 +1,27 @@
image:
repository: ghcr.io/proconnect-gouv/oidc2fer
repository: lasuite/oidc2fer
pullPolicy: Always
tag: "v1.0.13"
tag: "v1.0.4"
satosa:
replicas: 2
env:
- name: BASE_URL
value: https://renater.agentconnect.gouv.fr
- name: GUNICORN_CMD_ARGS
value: --workers=3
envVars:
BASE_URL: https://renater.agentconnect.gouv.fr
GUNICORN_CMD_ARGS: --workers=3
- name: LOG_LEVEL
value: INFO
- name: LOG_LEVELS
value: '{ "satosa.backends.saml2": "DEBUG" }'
LOG_LEVEL: debug
- 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
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: 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 } }
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: 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"
}
OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } }
ingress:
enabled: true
@@ -1,47 +1,27 @@
image:
repository: ghcr.io/proconnect-gouv/oidc2fer
repository: lasuite/oidc2fer
pullPolicy: Always
tag: "latest"
tag: "main"
satosa:
replicas: 2
env:
- name: BASE_URL
value: https://oidc2fer-staging.beta.numerique.gouv.fr
- name: GUNICORN_CMD_ARGS
value: --workers=3
envVars:
BASE_URL: https://oidc2fer-staging.beta.numerique.gouv.fr
GUNICORN_CMD_ARGS: --workers=3
- name: LOG_LEVEL
value: DEBUG
- name: LOG_LEVELS
value: '{ "satosa.backends.saml2": "DEBUG" }'
LOG_LEVEL: debug
- 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
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: 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 } }
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: OIDC_DB_URI
value: "stateless://:$(STATE_ENCRYPTION_KEY)@localhost"
- name: SIRET_MAP
value: |-
{
"https://test-idp.federation.renater.fr/idp/shibboleth": "12345678200010"
}
OIDC_DB_URI: { secretKeyRef: { name: redis.redis.libre.sh, key: url } }
ingress:
enabled: true
+5
View File
@@ -0,0 +1,5 @@
apiVersion: v2
name: extra
description: A Helm chart to add some manifests to oidc2fer
type: application
version: 0.1.0
+7
View File
@@ -0,0 +1,7 @@
apiVersion: core.libre.sh/v1alpha1
kind: Redis
metadata:
name: redis
namespace: {{ .Release.Namespace | quote }}
spec:
disableAuth: false
@@ -1,3 +1,35 @@
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:
@@ -14,26 +46,4 @@ 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
@@ -1,24 +0,0 @@
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"
@@ -1,51 +0,0 @@
{{/*
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 }}
@@ -1,71 +0,0 @@
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 }}
@@ -1,38 +0,0 @@
{{- 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 }}
@@ -1,16 +0,0 @@
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
@@ -1,71 +0,0 @@
# 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,6 +50,46 @@ 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
+4 -7
View File
@@ -1,6 +1,5 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "oidc2fer.fullname" . -}}
{{- $port := .Values.satosa.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
@@ -34,15 +33,13 @@ spec:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
{{- range .Values.ingress.paths }}
- path: {{ .path | quote }}
pathType: {{ .pathType }}
- path: /
pathType: Prefix
backend:
service:
name: {{ $fullName }}-satosa
name: {{ include "oidc2fer.satosa.fullname" . }}
port:
number: {{ $port }}
{{- end }}
number: {{ .Values.satosa.service.port }}
{{- with .Values.ingress.customBackends }}
{{- toYaml . | nindent 10 }}
{{- end }}
@@ -1,3 +1,4 @@
{{- $envVars := include "oidc2fer.common.env" (list . .Values.satosa) -}}
{{- $fullName := include "oidc2fer.satosa.fullname" . -}}
{{- $component := "satosa" -}}
apiVersion: apps/v1
@@ -41,9 +42,10 @@ spec:
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.satosa.env }}
env: {{ toJson . }}
{{- end }}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- with .Values.satosa.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
@@ -1,3 +1,4 @@
{{- $envVars := include "oidc2fer.common.env" (list . .Values.satosa) -}}
{{- $fullName := include "oidc2fer.satosa.fullname" . -}}
{{- $component := "satosa" -}}
apiVersion: v1
+14 -8
View File
@@ -21,6 +21,10 @@ 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
@@ -29,9 +33,7 @@ ingress:
enabled: false
className: null
host: oidc2fer.example.com
paths:
- path: '/$|^/\.well-known/openid-configuration$|^/images/|^/Saml2/|^/OIDC/|^/ping$'
pathType: ImplementationSpecific # enables regex matching
path: /
## @param ingress.hosts Additional host to configure for the Ingress
hosts: []
# - chart-example.local
@@ -46,9 +48,6 @@ ingress:
## @param ingress.customBackends Add custom backends to ingress
customBackends: []
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
## @section satosa
satosa:
@@ -75,8 +74,15 @@ satosa:
## @param satosa.securityContext Configure satosa Pod security context
securityContext: null
## @param satosa.env Configure satosa container environment variables
env: []
## @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.podAnnotations Annotations to add to the satosa Pod
podAnnotations: {}
-3
View File
@@ -31,6 +31,3 @@ attributes:
uid:
openid:
- uid
siret:
openid:
- siret
@@ -1 +0,0 @@
from .entity_id_to_siret_mapper import EntityIdToSiretMapper
@@ -1,23 +0,0 @@
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: false
memorize_idp: false
use_memorized_idp_when_force_authn: false
send_requester_id: false
enable_metadata_reload: false
mirror_force_authn: 'no'
memorize_idp: 'no'
use_memorized_idp_when_force_authn: 'no'
send_requester_id: 'no'
enable_metadata_reload: 'no'
acs_selection_strategy: prefer_matching_host
sp_config:
name: Passerelle OIDC vers FER
@@ -43,7 +43,6 @@ config:
- uid
- given_name
- usual_name
- siret
# Set code/token lifetimes as short as possible
authorization_code_lifetime: 60
@@ -57,5 +56,8 @@ config:
- given_name
usual_name:
- usual_name
siret:
- siret
extra_id_token_claims:
oidc-test-client:
- acr
agent-connect:
- acr
@@ -13,8 +13,4 @@ config:
# later in the PrimaryIdentifier processor.
- ".+"
eduPersonAffiliation:
- "^faculty$"
- "^staff$"
- "^employee$"
- "^researcher$"
- "^teacher$"
@@ -1,5 +0,0 @@
module: oidc2fer.attribute_generators.EntityIdToSiretMapper
name: EntityIdToSiretMapper
config:
attribute: siret
mapping_json: !ENV SIRET_MAP
-1
View File
@@ -17,7 +17,6 @@ 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
+19 -16
View File
@@ -11,44 +11,47 @@ version = "0.1.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: System Administrators",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.11",
]
description = "A gateway from RENATER's FER to ProConnect's OIDC provider, built on top of SATOSA."
description = "An application to handle contacts and teams."
keywords = ["OIDC", "SAML", "Shibboleth", "FER", "RENATER"]
license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.14"
requires-python = ">=3.11"
dependencies = [
"SATOSA==8.5.1",
"gunicorn==25.1.0",
"JSON-log-formatter==1.1.1",
"WhiteNoise==6.12.0",
"SATOSA==8.4.0",
"gunicorn==22.0.0",
"redis==5.0.4",
"JSON-log-formatter==1.0",
"WhiteNoise==6.7.0",
]
[project.urls]
"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"
"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"
[project.optional-dependencies]
dev = [
"pylint==3.1.0",
"pytest-cov==4.1.0",
"pytest==9.0.2",
"ruff==0.15.4",
"pytest-playwright==0.7.2",
"pytest==8.0.2",
"ruff==0.2.2",
"pytest-playwright==0.4.4",
]
[tool.setuptools]
packages = { find = { where = ["."], exclude = ["tests"] } }
zip-safe = true
[tool.distutils.bdist_wheel]
universal = true
[tool.ruff]
exclude = [
".git",
+7
View File
@@ -0,0 +1,7 @@
#!/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()
@@ -1,42 +0,0 @@
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
+19 -62
View File
@@ -1,9 +1,5 @@
import concurrent.futures
import json
import logging
import os
import time
import urllib.request
import pytest
from playwright.sync_api import Browser, Page, expect
@@ -24,7 +20,6 @@ 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()
@@ -36,7 +31,7 @@ def oidc_to_renater(
expected_given_name="Georges",
expected_usual_name="Grospieds",
):
page.goto("https://oidc-test-client.127.0.0.1.nip.io")
page.goto("https://oidc-test-client.traefik.me")
renater_wayf(page)
renater_test_idp(page, login=login)
@@ -45,7 +40,6 @@ 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 {
@@ -54,9 +48,8 @@ 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, access_token
return id_token
@pytest.mark.skipif(
@@ -64,116 +57,80 @@ 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.127.0.0.1.nip.io")
page.goto("https://oidc-test-client.traefik.me")
renater_wayf(page)
renater_test_idp(page, login="etudiant1")
expect(page.locator("pre")).to_contain_text('"error":"access_denied"')
def pro_connect_login(page: Page, email):
def agent_connect_login(page: Page, email):
page.goto("https://fsa1v2.integ01.dev-agentconnect.fr/")
page.get_by_role("button", name="Sidentifier avec ProConnect").click()
page.get_by_label("Connexion à AgentConnect").click()
page.get_by_label("Email professionnel").fill(email)
page.get_by_test_id("interaction-connection-button").click()
def pro_connect_to_renater(
def agent_connect_to_renater(
page: Page,
login="enseignant1",
expected_email="georges.grospieds@formation.renater.fr",
expected_given_name="Georges",
expected_usual_name="Grospieds",
):
pro_connect_login(page, email=expected_email)
agent_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("#userinfo")
text = page.inner_text("#json")
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_PC" not in os.environ, reason="Depends on staging deployment"
"TEST_E2E_AC" not in os.environ, reason="Depends on staging deployment"
)
def test_pro_connect_to_renater_keeps_sub(browser: Browser):
def test_agent_connect_to_renater_keeps_sub(browser: Browser):
with browser.new_context().new_page() as page:
result1 = pro_connect_to_renater(page)
result1 = agent_connect_to_renater(page)
with browser.new_context().new_page() as page:
result2 = pro_connect_to_renater(page)
result2 = agent_connect_to_renater(page)
assert result1["sub"] == result2["sub"]
@pytest.mark.skipif(
"TEST_E2E_PC" not in os.environ, reason="Depends on staging deployment"
"TEST_E2E_AC" not in os.environ, reason="Depends on staging deployment"
)
def test_pro_connect_to_renater_student_not_allowed(page: Page):
pro_connect_login(page, email="jean.dupont@formation.renater.fr")
def test_agent_connect_to_renater_student_not_allowed(page: Page):
agent_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("access_denied")
expect(page.locator("body")).to_contain_text("Une erreur technique est survenue.")
@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://oidc2fer.127.0.0.1.nip.io/images/logo.svg")
page.goto("https://satosa.traefik.me/images/logo.svg")
expect(page.locator("svg")).to_be_visible()
+5 -14
View File
@@ -26,8 +26,7 @@ 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
kind --name=oidc2fer delete cluster || true
cat <<EOF | kind --name=oidc2fer create cluster --config=-
cat <<EOF | kind create cluster --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
@@ -36,6 +35,7 @@ containerdConfigPatches:
config_path = "/etc/containerd/certs.d"
nodes:
- role: control-plane
image: kindest/node:v1.27.3
kubeadmConfigPatches:
- |
kind: InitConfiguration
@@ -50,7 +50,9 @@ 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
@@ -62,7 +64,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 --name=oidc2fer get nodes); do
for node in $(kind 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"]
@@ -89,17 +91,6 @@ 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"}]'