Compare commits

...

21 Commits

Author SHA1 Message Date
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
15 changed files with 108 additions and 66 deletions
+5 -25
View File
@@ -65,29 +65,9 @@ jobs:
needs:
- build-and-push
steps:
-
uses: actions/create-github-app-token@v1
id: app-token
- uses: numerique-gouv/action-argocd-webhook-notification@main
id: notify
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
deployment_repo_path: "${{ github.repository }}"
argocd_webhook_secret: "${{ secrets.ARGOCD_PREPROD_WEBHOOK_SECRET }}"
argocd_url: "${{ vars.ARGOCD_PREPROD_WEBHOOK_URL }}"
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
helmfile-lint:
runs-on: ubuntu-latest
container:
image: ghcr.io/helmfile/helmfile:latest
image: ghcr.io/helmfile/helmfile:v0.171.0
steps:
-
uses: numerique-gouv/action-helmfile-lint@main
+12
View File
@@ -6,6 +6,18 @@ 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.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
+5 -11
View File
@@ -1,17 +1,11 @@
# ---- base image to inherit from ----
FROM python:3.11-slim-bookworm as common
FROM python:3.11.12-slim-bookworm AS common
# Install xmlsec1 dependencies required for xmlsec (for SAML)
# Needs to be kept before the `pip install`
RUN 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/*
# We want the most up-to-date stable pip release
@@ -19,7 +13,7 @@ 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 +35,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
FROM common AS development
# Playwright browsers
ENV PLAYWRIGHT_BROWSERS_PATH=/pw-browsers
@@ -67,7 +61,7 @@ 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/
+7
View File
@@ -63,6 +63,13 @@ number and date.
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
+1 -1
View File
@@ -13,7 +13,7 @@ services:
BASE_URL: https://satosa.traefik.me
GUNICORN_CMD_ARGS: --workers=2
TEST_E2E: 1
#TEST_E2E_AC: 1
#TEST_E2E_PC: 1
OIDC_DB_URI: redis://redis/0
CLIENT_DB_JSON: |
{
+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,7 +1,7 @@
image:
repository: lasuite/oidc2fer
pullPolicy: Always
tag: "v1.0.4"
tag: "v1.0.6"
satosa:
replicas: 2
@@ -9,7 +9,8 @@ satosa:
BASE_URL: https://renater.agentconnect.gouv.fr
GUNICORN_CMD_ARGS: --workers=3
LOG_LEVEL: info
LOG_LEVEL: INFO
LOG_LEVELS: '{ "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
@@ -9,7 +9,8 @@ satosa:
BASE_URL: https://oidc2fer-staging.beta.numerique.gouv.fr
GUNICORN_CMD_ARGS: --workers=3
LOG_LEVEL: debug
LOG_LEVEL: DEBUG
LOG_LEVELS: '{ "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
+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 }}
+6 -1
View File
@@ -33,7 +33,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,6 +50,9 @@ ingress:
## @param ingress.customBackends Add custom backends to ingress
customBackends: []
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
## @section satosa
satosa:
@@ -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
@@ -13,4 +13,8 @@ config:
# later in the PrimaryIdentifier processor.
- ".+"
eduPersonAffiliation:
- "^faculty$"
- "^staff$"
- "^employee$"
- "^researcher$"
- "^teacher$"
+2 -2
View File
@@ -23,8 +23,8 @@ license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"SATOSA==8.4.0",
"gunicorn==22.0.0",
"SATOSA==8.5.1",
"gunicorn==23.0.0",
"redis==5.0.4",
"JSON-log-formatter==1.0",
"WhiteNoise==6.7.0",
+12 -12
View File
@@ -75,26 +75,26 @@ def test_oidc_to_renater_student_not_allowed(page: Page):
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",
@@ -106,22 +106,22 @@ def agent_connect_to_renater(
@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")