From 997621d75bc7890c078acf690691073bebd604b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Tue, 4 Aug 2026 13:41:38 +0200 Subject: [PATCH 1/2] fix(shibboleth): update SWITCH edu-ID identity provider certificates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signing certificates of both edu-ID identity providers had expired, in January 2021 for production and October 2020 for the test environment. Replace them with the current ones, valid until 2033, taken from the SWITCH federation metadata and checked against their SHA-256 fingerprints. The test certificate was also truncated by one line, which made it impossible to parse. Co-Authored-By: Johnny MariƩthoz --- data/idp_certificates/eduid.crt | 38 +++++++++++++++------------- data/idp_certificates/eduidtest.crt | 39 ++++++++++++++++------------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/data/idp_certificates/eduid.crt b/data/idp_certificates/eduid.crt index 8c39d0e24..2f18881a7 100644 --- a/data/idp_certificates/eduid.crt +++ b/data/idp_certificates/eduid.crt @@ -1,17 +1,21 @@ -MIIDDDCCAfSgAwIBAgIVALoCDnpVfF76D6WKc9mDL9jtreW1MA0GCSqGSIb3DQEB -CwUAMBMxETAPBgNVBAMMCGVkdWlkLmNoMB4XDTE4MDExNzEyMDg0OFoXDTIxMDEx -NzEyMDg0OFowEzERMA8GA1UEAwwIZWR1aWQuY2gwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCbkUjiqUSI7m8LCNeIW/A3V+frMWd+DPvHzpEwIXLvzDPX -ts6lCGzI3Yo6lNkF7VMUh6mxEXegu3t8AbaOsfrroFijIxMdQ4Pq3Z/Ps2ssa0KA -xkcpHnWP011la1TN7hsxq07Xsat++CHVescJ8zk7Qd+uZmmfW5Kfj5KQhOPoQBMU -LVtak/Kjeolo4Ur1fwHtu8gvTvwrBVMy0wYMeylBmLL73dKYFO2mZLs5lP0FKXTb -mAUPF+WfkOckuExYvetWxLDyiDaxs5j3/KqJsDSiPQq1oGZkqvPfC4NzfCUTsuW5 -RdPierusXpnR4Wd8gj3feS4d6fGrt0arFUYkiyjFAgMBAAGjVzBVMB0GA1UdDgQW -BBR5UYvm41G9T4lRYQnVBamV5QQ2czA0BgNVHREELTArgghlZHVpZC5jaIYfaHR0 -cHM6Ly9lZHVpZC5jaC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0BAQsFAAOCAQEA -OHXDaEWgKwegR4pBMqSw4KmPNCn06Jf5cZ3sVCHKtDGhiVz9JwevujDVo94pcGPJ -XyJdvCn9gCRpheWJIYv94L3Do/eh7/jwWSp93+5vJdbVvE9EcMbooRO2fSIWZg6y -KVavIKLrItH9fUaY8mE1YSTzpAyj/bYHGZzGhd+23gaKZSMU2Oyjc8FwTWjDcZLC -+iUG7QOeK1rigVuP/HWqnawRfV5PQNCumhwvLTfgwj2d7hksXIimdfzUtQ1khGTo -mbpfFePiqKvBj5O5CmwuD3ccg0zFI1TQHUSussK/tOGz54MAyKG8M1txL/aTC92e -Sa95LKJp5+2fy/XNy2hBAg== \ No newline at end of file +MIID6jCCAlKgAwIBAgIUQYBVlHR1BTuCRvxNID/1YViWvXswDQYJKoZIhvcNAQEL +BQAwEzERMA8GA1UEAxMIZWR1aWQuY2gwHhcNMjMxMTAxMTAwMDA0WhcNMzMxMjE4 +MTAwMDA0WjATMREwDwYDVQQDEwhlZHVpZC5jaDCCAaIwDQYJKoZIhvcNAQEBBQAD +ggGPADCCAYoCggGBAJ7otyXaPNpqa2T4sIw3D+G9qdns5fGWYPyKkqkbSwM/xcWY +86JTyG4bNaI9za1mVmX/+JZ3rNjmihHh1pNc8Y4U3sroX4YmpHme7U0o5QzgpXgt +g23NyWGSTKJG4Z4LhoPXvBvcwhTE2wRbcIiiHQExXrmntq6QXafum2eSy0wuQcsd +y+jiJX1shyDC29Epf1ObglfbUYS1GkfBaV9QfsKDbEBWixdALTpubcxmEHONdmNn +8wrr2IPkVod+pBgLGaacRHKt/O7aw3R5FlAOw8KANJMU9MDrp7yhH3HzQPMyc5E8 +HNIbh284NY3etkj0rDtI8Py2DRjMkZeUdkqyCmrskwd0aUXKTOjBT0u3TD4hayuI +UUx7w3Fb3MaEWY16mFVrlw5/6AsRIiFQGbxXwXV41oTxiSBGAYBoux27Db5j6H3X +EseKamk5oH8z/4HqhtzCA5oMCYkwWpf2NXwJks6T378bSw0VSklQXcoBcwPjByyr +ZVHpV+8bOzJdbEfRHQIDAQABozYwNDATBgNVHREEDDAKgghlZHVpZC5jaDAdBgNV +HQ4EFgQUUXM4hK0yc1TiLRbbgkfB6FZ6jyowDQYJKoZIhvcNAQELBQADggGBAJN1 +C/JIa4hfpb7HwdSGQ/fnxRV+m/V1wp5LEZKEd/P1eIjb3ETkUL4wP90a+B22ndJI +F46FqmRwu1KtVufCQE1ptYJ7JqmQEg5v1rHKMzV8CYEIZm9Wlb/dto15TAs/CkQS +Hyzpaeg4cNpruGtiIPUB1q01UM8NEWWU9vgrvwJjj3j3/mJPN6FWtPtZDCnIojED +V3QaeXzxQhPn5UsJuCjWX2Y81AQdVjSkbKAxyy9BWIRV2Ib6FWBZzVQwbjFADZKT +iIortzAVYCrJHHU/c5enI19U6Y8/+xzu97w3STiQLgvlUUhNNgmaYMKYtRWSrqW1 +aaSIQ4tiylNLEb0QdoG03mDZ60WUsRF87ZrlTYU+vMNj3d4dsF70mLK+0IEyKud2 +r/76LQg8AEF6ESX8i3n6IGG3Tt1zeBbVZ4zNtFjvzmef7CYyLsu5MP1QBGI4QLV1 +9rBn+6JszkavM972cTuZTVZ1BU2/g/4ZSq85+XoKRmA2GH+Wega5gX63BIOnAQ== diff --git a/data/idp_certificates/eduidtest.crt b/data/idp_certificates/eduidtest.crt index f72f255b7..1065eb6e1 100644 --- a/data/idp_certificates/eduidtest.crt +++ b/data/idp_certificates/eduidtest.crt @@ -1,17 +1,22 @@ -MIIDIDCCAgigAwIBAgIVANt+in4DKHpdPEdzf7EKsoI+xrUyMA0GCSqGSIb3DQEB -CwUAMBgxFjAUBgNVBAMMDXRlc3QuZWR1aWQuY2gwHhcNMTcxMDMxMDgyNTQzWhcN -MjAxMDMxMDgyNTQzWjAYMRYwFAYDVQQDDA10ZXN0LmVkdWlkLmNoMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAobHkbul8u6SEpc4C3/fkkwGYcthsFQC+ -ILWKrK7D2Mnt/OdZrkFrNVCaDi6dl/T6tN8ESrpCxqG9jUxlgxHr0L2LlN2ilqiB -FkY1zM/OkJa2WarQZ9dkP4dfjLdp2T17MkPZvTCMoHqzyK7WvFCGAa5JxXubKmh+ -KjuNzGA/4Y3Vm+Y8Qs/wbpHeMXVk+Oeke6Qnqs1JfMXIWGD81AYbUjHaBIhF1J5u -8iJe0zTJ8MSuPdMOLEY/4KSWKtT0HM/LRAH4iRKdq+B3Cl+WZGKgd864GQ1HkxHZ -1xx7H1TisWzUIyeeuO1n6MNa9+kcLal2p2C7q+wuC8BWLDXlut+VxwIDAQABo2Ew -XzAdBgNVHQ4EFgQUfGdP74lJsTJldXc4hVRkM5KX6oEwPgYDVR0RBDcwNYINdGVz -dC5lZHVpZC5jaIYkaHR0cHM6Ly90ZXN0LmVkdWlkLmNoL2lkcC9zaGliYm9sZXRo -MA0GCSqGSIb3DQEBCwUAA4IBAQCUBMahAkFjp+9B5fMjqM6ZCj/zC7UgM2DBBxm4 -YQaSSfjNGz1YC0fVDpoH1qVf0m9Ig7tI4QOAipzlwrKgCbB++KTxsXQBtfw2EBbg -hfNphXCU0b855FUirnJWN+T2t3APq68dII3KkRPQ8uSq7JN7Ccm38RzmbQ9gt1RR -4IVqHf/TstovqXC1sf/bDM9IBqvsZyD/043i1uwOQyiuZyOUS5J15BnKmo2N0NLz -9E5IDu+3NpnlwBcqCaXPZhYSoTGZJfPE8MPzlP3tDoElEK07dOzyOF8mlnioqd0q -OKNguza3bi3RRsZUlDAJHSGHm+Fc3QLUPCDLZc0+OKcQbThC \ No newline at end of file +MIIEETCCAnmgAwIBAgIUPdBt/Z2aCedplRF6Qy/8E7lPPY8wDQYJKoZIhvcNAQEL +BQAwGDEWMBQGA1UEAwwNdGVzdC5lZHVpZC5jaDAeFw0yMzA5MTMwNjIyMjNaFw0z +MzA5MTAwNjIyMjNaMBgxFjAUBgNVBAMMDXRlc3QuZWR1aWQuY2gwggGiMA0GCSqG +SIb3DQEBAQUAA4IBjwAwggGKAoIBgQCbv+wTgRUGXCKCpoiJR0cdR5ik8DZtytWi +t06KA9oAgY2bdfMVgRKqUoALQkOoEeZg7pOzuvN6PgKKikKWUbMZozYEpKr6GAxL +VY5ZBieWQkI/wr40YZhU8tnwpU/IIekXzNziG8jkGflbWBpQ2cUN9GQ8/RwkDj0u +HeiivW4pgVHM4i9uFA8ZZnJ1XBBPyCUKTjsg94M8MTrfBe1XPzkjpYtP6czF5oJj +LAyylKWBGnuR4BHdgisKSYQPS4qg8s7S4VQA0Xa0CpBGIyopnhDBUDP23S9pVt1R +SbvV1tB4rnpyPHEmiW/vR4/pFBm+jv6YLCrMOt7UFNeLtaoA5XbXw53bBUh8Lde3 +FP8QvoPE6rj1XY7LluY0Sb4JvHjJaX9kG4Fmjknx7fm0QR2FXVcoU1utxt0R1rlr +ov1GN3P1fhy6ADU9pwd7XXcDaIjJyeTkbld2YJs3iS/ZhD9PoJGJrPwdC9eFL5jf +ao8RwuR1y4Gshou32XG6Os5o0r0wjrECAwEAAaNTMFEwHQYDVR0OBBYEFGAAmC/z +8UQCszlkME/KHZvOgNW5MB8GA1UdIwQYMBaAFGAAmC/z8UQCszlkME/KHZvOgNW5 +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggGBAARtn9tUlkxwxecP +J0zhI58SzpdcoE1+Qo9qF+sWIdzIAZ8ev8LBHfHhpXjOBlQQv8hIhs8U9hTJx1c+ +Jq94ftTSQ96FTZ9BmDaw7iQmorDSZeMxss6RXROF0ORJJ9kdAhTyMJj3zAfPlB4/ +32WbFlS9AtZdEfLdHNEmlxKtWFnaNasYFUh8oMpkaXnVToAp0H0qpZbzfwpI85/4 +S1zwwM62v+JyanUWX8BUSlB7L5cBa+6BRiUMkr0OFk0Vx9nM1GOkEfHttSdq7OnW +0QeMdXYo417/DsCDxrD9A/SH1a+w3odYmbjl9lFr83wS8nApZtWG+7matS4CqI6a +QjPRt0yZKQqc3vA5qvGWjQAITaNXlLVIRtTDP2Qs3cU0UUA0vqYX/CkLnGHCppqd +GHLxP3n9LluZJs+OmArbgdifGm+7xgZMbmuAnoDCsTxNoSYqxHgU9vuETyjXTkFC +2Wy9XopqcmwoW7fP44EShC4kp6sHgl+u2Ms+kjHGVhkvt+fyLw== From 498f65648a7ef7a178f6eb48a66bd1398a0f8c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Tue, 4 Aug 2026 13:42:11 +0200 Subject: [PATCH 2/2] fix(certificates): generate development certificates instead of versioning them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three private keys were versioned in this public repository, exposed since 2018 and 2019: the TLS key of the development server, its duplicate in the HAProxy bundle, and the SAML service provider key. All the versioned certificates had expired as well, which left the HTTPS development server unusable by any modern browser. Certificates are now generated where they are needed: - `scripts/gen-certs` produces the development server pair in `.certs/`, using mkcert when available so that browsers accept it without warning, and falling back to openssl elsewhere. It is called by the bootstrap, which never regenerates an existing pair unless `--force-certs` is given, and skips the step on deploy. - The nginx and HAProxy images generate their own certificate at build time. HAProxy needs a separate stage, its base image ships no openssl command. - The tests build an ephemeral key pair in a temporary directory, so the suite no longer depends on any certificate file. The service provider key pair has no default any more. It identifies the instance to the identity providers, so it has to be registered in the SWITCH edu-ID resource registry and cannot be generated locally: each environment provides its own and references it in `invenio.cfg`. Until then SAML stays disabled and the SWITCHaai button is hidden rather than leading to an error. The identity providers certificates directory becomes configurable, which also removes the constraint of running the application from the repository root. `scripts/server` now verifies the certificates before starting anything. The check used to run after the celery worker had been spawned, and the cleanup trap was registered later still, so a missing pair left an orphaned worker behind. The worker PID was also captured in a differently spelled variable than the one the trap and the final wait read; spelling it consistently makes both cover the worker on a normal exit too. Co-Authored-By: Johnny MariƩthoz --- .dockerignore | 2 + .gitignore | 3 + INSTALL.md | 31 +++++++ MANIFEST.in | 2 +- data/idp_certificates/idp.crt | 17 ---- docker/haproxy/Dockerfile | 15 ++- docker/haproxy/haproxy.cfg | 6 ++ docker/haproxy/haproxy_cert.pem | 83 ----------------- docker/nginx/Dockerfile | 10 +- docker/nginx/sp.key | 40 -------- docker/nginx/sp.pem | 93 ------------------- docker/nginx/test.crt | 31 ------- docker/nginx/test.key | 52 ----------- pyproject.toml | 1 + scripts/bootstrap | 14 +++ scripts/gen-certs | 53 +++++++++++ scripts/server | 16 +++- .../modules/shibboleth_authenticator/auth.py | 4 +- .../shibboleth_authenticator/config.py | 17 +++- sonar/modules/shibboleth_authenticator/ext.py | 16 +++- sonar/modules/utils.py | 6 ++ .../theme/templates/sonar/accounts/login.html | 2 + .../templates/sonar/accounts/signup.html | 2 + tests/conftest.py | 63 ++++++++++++- tests/ui/shibboleth_authenticator/conftest.py | 6 +- .../test_shibboleth_auth.py | 33 ++++--- tests/ui/test_utils.py | 4 + tests/ui/test_views.py | 9 ++ 28 files changed, 279 insertions(+), 352 deletions(-) delete mode 100644 data/idp_certificates/idp.crt delete mode 100644 docker/haproxy/haproxy_cert.pem delete mode 100644 docker/nginx/sp.key delete mode 100644 docker/nginx/sp.pem delete mode 100644 docker/nginx/test.crt delete mode 100644 docker/nginx/test.key create mode 100755 scripts/gen-certs diff --git a/.dockerignore b/.dockerignore index 43ef69b62..5c976e079 100644 --- a/.dockerignore +++ b/.dockerignore @@ -17,3 +17,5 @@ Procfile* celerybeat-schedule .venv/ + +.certs/ diff --git a/.gitignore b/.gitignore index 81ba9b5ad..6e6afdd71 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,6 @@ celerybeat-schedule* data/wiki/_index/ .python-version + +# Development certificates, generated by scripts/gen-certs +.certs/ diff --git a/INSTALL.md b/INSTALL.md index ecce9692b..d7e26f52f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -73,6 +73,37 @@ Start the webserver and the celery worker: uv run poe server ``` +The server listens on . Its certificate is generated by +the bootstrap into `.certs/`, which is never versioned. Run `uv run poe certs` +to create it again, or `./scripts/gen-certs --force` to renew it. + +If [mkcert](https://github.com/FiloSottile/mkcert) is installed, the certificate +is signed by a local authority. Install that authority once so that browsers +accept the certificate without warning: + +```console +mkcert -install +``` + +This matters for SWITCH edu-ID authentication: the identity provider answers +with an auto-submitted POST, and clicking through a browser warning loses it. + +### SWITCH edu-ID authentication + +SAML authentication is disabled until a service provider key pair is configured. +The certificate identifies the instance to the identity providers, so it has to +be registered in the SWITCH edu-ID resource registry: it cannot be generated +locally. Provide the pair outside of the repository and reference it in +`invenio.cfg`: + +```python +SHIBBOLETH_SERVICE_PROVIDER_CERTIFICATE = "/path/to/sp-cert.pem" +SHIBBOLETH_SERVICE_PROVIDER_PRIVATE_KEY = "/path/to/sp-key.pem" +``` + +Without them the application runs normally and no edu-ID button is offered on +the login page. + Start a Python shell: ```console diff --git a/MANIFEST.in b/MANIFEST.in index ab714f50f..504c2f8d6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -43,7 +43,7 @@ prune docs/_build recursive-include _sources *.txt recursive-include _static *.css *.js *.png recursive-include data *.crt *.csv *.jpg *.json *.md *.png -recursive-include docker *.cfg *.conf *.crt *.ini *.key *.pem *.sh +recursive-include docker *.cfg *.conf *.ini *.sh recursive-include docs *.bat *.py *.rst *.txt Makefile recursive-include sonar *.babelrc *.eslintignore *.gitkeep *.json *.html *.js *.scss *.css *.png *.jpg *.svg *.po *.pot *.mo *.py *.txt *.woff *.woff2 recursive-include tests *.doc *.json *.pdf *.py *.xml *.jpg *.csv diff --git a/data/idp_certificates/idp.crt b/data/idp_certificates/idp.crt deleted file mode 100644 index 2d8cab416..000000000 --- a/data/idp_certificates/idp.crt +++ /dev/null @@ -1,17 +0,0 @@ -MIIDIDCCAgigAwIBAgIVANt+in4DKHpdPEdzf7EKsoI+xrUyMA0GCSqGSIb3DQEB -CwUAMBgxFjAUBgNVBAMMDXRlc3QuZWR1aWQuY2gwHhcNMTcxMDMxMDgyNTQzWhcN -MjAxMDMxMDgyNTQzWjAYMRYwFAYDVQQDDA10ZXN0LmVkdWlkLmNoMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAobHkbul8u6SEpc4C3/fkkwGYcthsFQC+ -ILWKrK7D2Mnt/OdZrkFrNVCaDi6dl/T6tN8ESrpCxqG9jUxlgxHr0L2LlN2ilqiB -FkY1zM/OkJa2WarQZ9dkP4dfjLdp2T17MkPZvTCMoHqzyK7WvFCGAa5JxXubKmh+ -KjuNzGA/4Y3Vm+Y8Qs/wbpHeMXVk+Oeke6Qnqs1JfMXIWGD81AYbUjHaBIhF1J5u -8iJe0zTJ8MSuPdMOLEY/4KSWKtT0HM/LRAH4iRKdq+B3Cl+WZGKgd864GQ1HkxHZ -1xx7H1TisWzUIyeeuO1n6MNa9+kcLal2p2C7q+wuC8BWLDXlut+VxwIDAQABo2Ew -XzAdBgNVHQ4EFgQUfGdP74lJsTJldXc4hVRkM5KX6oEwPgYDVR0RBDcwNYINdGVz -dC5lZHVpZC5jaIYkaHR0cHM6Ly90ZXN0LmVkdWlkLmNoL2lkcC9zaGliYm9sZXRo -MA0GCSqGSIb3DQEBCwUAA4IBAQCUBMahAkFjp+9B5fMjqM6ZCj/zC7UgM2DBBxm4 -YQaSSfjNGz1YC0fVDpoH1qVf0m9Ig7tI4QOAipzlwrKgCbB++KTxsXQBtfw2EBbg -hfNphXCU0b855FUirnJWN+T2t3APq68dII3KkRPQ8uSq7JN7Ccm38RzmbQ9gt1RR -4IVqHf/TstovqXC1sf/bDM9IBqvsZyD/043i1uwOQyiuZyOUS5J15BnKmo2N0NLz -9E5IDu+3NpnlwBcqCaXPZhYSoTGZJfPE8MPzlP3tDoElEK07dOzyOF8mlnioqd0q -OKNguza3bi3RRsZUlDAJHSGHm+Fc3QLUPCDLZc0+OKcQbThC diff --git a/docker/haproxy/Dockerfile b/docker/haproxy/Dockerfile index 9f5654595..853431dfc 100644 --- a/docker/haproxy/Dockerfile +++ b/docker/haproxy/Dockerfile @@ -1,7 +1,20 @@ # SPDX-FileCopyrightText: Fondation RERO+ # SPDX-License-Identifier: AGPL-3.0-or-later +# Self signed certificate generated at build time, so that no private key is +# versioned. Built in a separate stage because the haproxy image ships no +# openssl command. In production the external certificate is provided by the +# deployment, through Let's Encrypt. +FROM alpine:3 AS certificate +RUN apk add --no-cache openssl \ + && openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ + -keyout /cert.key -out /cert.crt \ + -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \ + && cat /cert.crt /cert.key > /cert.pem + FROM haproxy:1.8 RUN mkdir -p /usr/local/var/lib/haproxy/ COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg -COPY haproxy_cert.pem /usr/local/etc/cert.pem +# HAProxy expects the certificate and its key concatenated in a single file. +COPY --from=certificate /cert.pem /usr/local/etc/cert.pem diff --git a/docker/haproxy/haproxy.cfg b/docker/haproxy/haproxy.cfg index 9a5e245dd..dc2f4d47a 100644 --- a/docker/haproxy/haproxy.cfg +++ b/docker/haproxy/haproxy.cfg @@ -41,6 +41,12 @@ frontend in-stats stats uri / stats hide-version +# "ssl verify none" is deliberate on both backends: this configuration only +# serves the local demonstration stack, where the frontend certificate is a self +# signed one generated when its image is built. The link stays inside the docker +# network, so it is encrypted but not authenticated. Any deployment where this +# link crosses a machine boundary should sign the frontend certificate with an +# internal authority and switch to "verify required ca-file ...". backend ssl_app balance leastconn http-check disable-on-404 diff --git a/docker/haproxy/haproxy_cert.pem b/docker/haproxy/haproxy_cert.pem deleted file mode 100644 index c4c2ebd74..000000000 --- a/docker/haproxy/haproxy_cert.pem +++ /dev/null @@ -1,83 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFQjCCAyoCCQCb5O/0dce0ojANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJD -SDEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEKMAgGA1UECgwBLjEKMAgGA1UECwwB -LjESMBAGA1UEAwwJbG9jYWxob3N0MRAwDgYJKoZIhvcNAQkBFgEuMB4XDTE5MDQw -NDE0MzkxOFoXDTIwMDQwMzE0MzkxOFowYzELMAkGA1UEBhMCQ0gxCjAIBgNVBAgM -AS4xCjAIBgNVBAcMAS4xCjAIBgNVBAoMAS4xCjAIBgNVBAsMAS4xEjAQBgNVBAMM -CWxvY2FsaG9zdDEQMA4GCSqGSIb3DQEJARYBLjCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAMdOkUXeK1UPD+eyvfJTCCu09U09DeRH0xHso2ZXHzNHAulc -TF8Yx9kkTC6IHTSZX9Of77k/SoH3V2Tii1fBa06N3Xa8PAZ8pBxPsxiH8czdabBU -WYxgZ/n8Y/K5DvJRyspZB/2SxGMRXnRVnUgqXti3MFfHvU+Z9PXcgBOPi0jRfxqO -EznJjyXoLPY1Valdqbr3Ew97tpksxKDXCoKHyq0GoGQZC2iYMl8pxEzVYnGZ3IYY -65bX6/49SuVxU1ECd79o7ozrHiVMNDQRfeQDJknG2HOmDiDvgU/MgCkJhAtg7Oz0 -Z1x7rSlY7zFBDmalDNzmRZ6dKZcvdjJv+JDPZsnOrvPc9fMOzAPHxKSNsQm9+5/I -bhj6iHLntb9cqhsB1sUgX+OcjERZihJDrgIym7cKm1SLOIyn7SUQfuPKRvsA9GOc -NmhMV+uIxFsDXK6yxdplSKvYXRsqGr9oD1txBjKZHAIqHM/2/UWwXgZr98t2iz9G -EZX4PXZVgokrDYryFJIEFttvRdeX4uqy+0kMTzW21ju1BqXIjFHTvR4dXaLgCBZW -cFCinWGp+G5d3b8w/myGYDQNDP7RjzDbV7yJKPMMPdq9/LehoTz9WUWetsPgj+Ye -aar15qanLDuZqUr0gmGMKlj05XwgE1fc3tvl/Nxv7nOmXld5agjfbQ75+GxvAgMB -AAEwDQYJKoZIhvcNAQELBQADggIBADG3AV9WDlZYsRdC+Le2YLwRTM2v2/mZ1Cgq -s8oktgk3upZI/6L09s2rbIHpEurEucwMxlEvZTDthKOafVYXJedLT4IFT9ezaP58 -eBLRS+LUlpKO7IHBEPe6FP5kXWiLpJlGgIMeOhJbb65x7eojbSOhATNo1vPRpg5w -xLKX+s0keAGUyuKvOkE+FSInT87zQeH29p4CqBxPZLJZfCzLb4FIgCz9FIZ7KEIW -h26QSBfboBIJ+H5RPWO8WsaH0jgFkTcmHs5oOVsZv3qQUVPJfEfyG007vcviw1ok -4uwNOv22KJDI/VBJ5UbzJOcD0BW0EMw7/1pcJ3itm17YvmAReTIOwDa0VMxGji1d -6PEni1EKiJyiKuEqluEbcWLV4A7LbqItxOobVVcZJPB/NwlV/vKwAzJJ4Q7kItSn -IpaDarV+P69gUWKGjWQCFLU3LtzUiK/jbGfAWa7V4PZ1M2q35wWQMzmj0jjidFFn -HxOc+aeq63+2TFaCh0D1XzG+dmtpPMO/nax6Ak0QSIBYjTvGjXhJM3vLIJZBdvmM -XVdZUoE6S0ha5LPK9xryKF20wIq0A/rZ/c3u2VnDPQKeMs6tCxhQcGIaGDHWSAFh -dO4oouQYaH5vDH5eESrwM22PJvjd5Lykv2l/oBFpMyrYkDh4Nqm3lqY/kB+W2U7m -ZnihdMEE ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -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 -ox5oXud+Wan4s1Aw/fVhc1akHSDfH2En ------END PRIVATE KEY----- diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index 1e53bf526..8a06257f6 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -5,5 +5,11 @@ FROM nginx COPY nginx.conf /etc/nginx/nginx.conf COPY conf.d/* /etc/nginx/conf.d/ -COPY test.key /etc/ssl/private/test.key -COPY test.crt /etc/ssl/certs/test.crt + +# Self signed certificate generated at build time, so that no private key is +# versioned. It is only used for the connection from the load balancer, which +# does not verify it. +RUN openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ + -keyout /etc/ssl/private/test.key -out /etc/ssl/certs/test.crt \ + -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:frontend,IP:127.0.0.1" diff --git a/docker/nginx/sp.key b/docker/nginx/sp.key deleted file mode 100644 index f70fa338e..000000000 --- a/docker/nginx/sp.key +++ /dev/null @@ -1,40 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -zUkmF4YN7sH1IeURI149LUha ------END PRIVATE KEY----- diff --git a/docker/nginx/sp.pem b/docker/nginx/sp.pem deleted file mode 100644 index 1d5062d0d..000000000 --- a/docker/nginx/sp.pem +++ /dev/null @@ -1,93 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 11240779440452688558 (0x9bff45584ab25aae) - Signature Algorithm: sha256WithRSAEncryption - Issuer: CN=sonar.ch - Validity - Not Before: Jul 2 13:27:23 2019 GMT - Not After : Jul 2 13:27:23 2022 GMT - Subject: CN=sonar.ch - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (3072 bit) - Modulus: - 00:a3:44:76:cd:9e:5e:51:64:1f:11:ee:fe:a3:0f: - 19:c8:c7:e6:e9:c1:ec:58:a9:cb:b0:45:42:d7:b3: - 78:f8:6f:23:0f:ab:23:7c:cd:71:14:80:b4:55:0b: - dd:a4:6a:d3:39:a9:bc:95:bd:d9:d5:aa:4c:25:24: - 6b:3e:d4:2a:e8:61:e3:c2:55:a0:e0:7d:fd:b7:40: - 9f:94:4e:52:f1:2d:09:3d:c8:32:4c:f1:7b:d9:5d: - c2:ed:23:a1:a9:f0:57:da:e5:a2:65:87:bf:89:1e: - 44:c7:2b:dc:6f:b7:3c:27:f2:df:85:f3:c9:7b:e7: - 75:2d:ef:85:2e:3c:97:35:17:5a:39:3c:ab:27:b5: - aa:38:7c:7c:88:48:b3:91:e0:fb:88:f0:fe:e0:64: - 16:53:c5:28:fc:b3:a5:9f:70:ed:cd:9e:af:a4:fe: - cb:9e:52:57:53:f0:ca:3a:b5:f3:05:bc:13:e6:e1: - d5:1c:9d:52:74:55:92:60:ad:13:86:af:e6:21:90: - 50:f2:78:57:c2:49:94:dd:1c:12:15:fc:ae:52:c3: - 15:5f:a3:09:f3:79:95:eb:6c:eb:03:a0:60:bd:65: - 86:32:7b:77:ac:3c:c8:ad:ce:2f:7a:14:07:96:58: - 8f:d5:75:28:44:d1:f2:42:4f:84:ea:7d:32:ad:cc: - ff:f7:8e:1a:20:ba:01:e5:2f:b5:7c:66:16:4b:82: - 27:80:c3:8a:81:5f:cc:f1:d2:54:c4:00:8c:14:80: - d9:2e:5b:9c:4a:e7:6f:fb:61:7e:30:ea:25:89:0b: - c5:5c:6c:b6:4e:62:c7:f7:bd:5d:33:d8:2c:a0:e9: - 4f:e4:6a:ad:bc:56:ac:f3:b6:de:c6:99:9d:ad:4d: - 75:d6:fd:ea:38:a2:c3:c0:16:72:8a:ed:08:ae:4a: - 56:ac:2b:51:a1:05:ef:e1:16:57:0c:ce:de:6c:6e: - b4:ce:d2:fe:40:d3:18:10:86:4c:4c:79:84:4a:7d: - f8:d0:03:75:53:67:b3:af:39:f3 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Subject Alternative Name: - DNS:sonar.ch, URI:https://sonar.ch/shibboleth - X509v3 Subject Key Identifier: - 4D:BA:B6:DD:18:94:97:53:4F:5B:C9:D0:35:62:A0:1C:17:09:2A:A5 - Signature Algorithm: sha256WithRSAEncryption - 2e:03:5c:27:12:e9:31:e3:ad:75:8c:18:fb:c2:c5:7f:67:52: - fc:98:e3:59:90:fa:ce:50:4e:ff:0f:5e:0b:4d:e0:fd:b7:19: - e7:15:e7:fa:b4:c5:43:f9:73:57:fa:6e:96:9b:d6:39:a0:ab: - 03:ac:70:42:d8:d7:31:de:00:fc:5a:0b:6f:7e:30:14:3e:91: - e4:70:f5:ca:2c:1e:4c:e7:1d:51:2e:bf:ff:cb:66:dc:a2:e8: - 3f:e1:bc:e1:7c:58:32:62:c9:83:53:b1:ba:01:5d:62:18:ba: - e0:a7:c8:d0:97:70:06:66:b5:68:e9:76:eb:83:04:f2:03:1a: - c1:92:ee:a9:40:4e:73:c2:20:15:03:75:97:ef:1f:cc:e3:58: - ee:04:59:36:fa:48:d9:e2:9a:67:5d:f1:99:8d:78:6d:5c:29: - af:f0:27:fc:63:78:ca:20:13:3e:30:c2:9d:5b:94:73:9d:ec: - f5:17:8c:4c:16:d3:c1:24:6a:0c:78:82:61:d4:ba:1e:88:7c: - 89:5f:35:b8:2a:3e:73:48:ea:ec:cb:a1:af:8f:0e:b8:72:6d: - 09:8d:15:29:82:ec:23:74:1a:31:41:43:f7:5e:e6:ec:ea:ad: - 4c:99:f5:44:07:0a:9d:a3:17:da:c7:9f:38:2e:90:c7:0b:fe: - 4d:74:ef:33:d4:a2:2e:d1:cb:d7:cb:e4:54:3e:cc:f9:63:9f: - f0:29:25:c5:50:f7:31:44:24:ff:73:fe:c9:8b:3e:01:6a:08: - cd:d1:80:81:90:b4:09:30:a9:34:be:77:2d:81:69:b3:7b:3e: - e8:0a:85:76:c4:fa:53:cb:3d:d0:f5:89:a3:de:88:81:9b:cb: - 1d:da:bb:5f:93:e1:34:7c:10:f4:a8:f7:6f:2f:31:22:c0:d8: - bd:5a:67:c0:5e:31:04:ba:5b:07:08:38:9e:53:fc:f4:6b:bd: - 94:64:26:cc:02:c8:ed:d4:a6:d8:0c:96:4a:f0:46:a1:c3:be: - 9c:be:bb:2c:08:a6 ------BEGIN CERTIFICATE----- -MIID/DCCAmSgAwIBAgIJAJv/RVhKslquMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV -BAMTCHNvbmFyLmNoMB4XDTE5MDcwMjEzMjcyM1oXDTIyMDcwMjEzMjcyM1owEzER -MA8GA1UEAxMIc29uYXIuY2gwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIB -gQCjRHbNnl5RZB8R7v6jDxnIx+bpwexYqcuwRULXs3j4byMPqyN8zXEUgLRVC92k -atM5qbyVvdnVqkwlJGs+1CroYePCVaDgff23QJ+UTlLxLQk9yDJM8XvZXcLtI6Gp -8Ffa5aJlh7+JHkTHK9xvtzwn8t+F88l753Ut74UuPJc1F1o5PKsntao4fHyISLOR -4PuI8P7gZBZTxSj8s6WfcO3Nnq+k/sueUldT8Mo6tfMFvBPm4dUcnVJ0VZJgrROG -r+YhkFDyeFfCSZTdHBIV/K5SwxVfownzeZXrbOsDoGC9ZYYye3esPMitzi96FAeW -WI/VdShE0fJCT4TqfTKtzP/3jhogugHlL7V8ZhZLgieAw4qBX8zx0lTEAIwUgNku -W5xK52/7YX4w6iWJC8VcbLZOYsf3vV0z2Cyg6U/kaq28Vqzztt7GmZ2tTXXW/eo4 -osPAFnKK7QiuSlasK1GhBe/hFlcMzt5sbrTO0v5A0xgQhkxMeYRKffjQA3VTZ7Ov -OfMCAwEAAaNTMFEwMAYDVR0RBCkwJ4IIc29uYXIuY2iGG2h0dHBzOi8vc29uYXIu -Y2gvc2hpYmJvbGV0aDAdBgNVHQ4EFgQUTbq23RiUl1NPW8nQNWKgHBcJKqUwDQYJ -KoZIhvcNAQELBQADggGBAC4DXCcS6THjrXWMGPvCxX9nUvyY41mQ+s5QTv8PXgtN -4P23GecV5/q0xUP5c1f6bpab1jmgqwOscELY1zHeAPxaC29+MBQ+keRw9cosHkzn -HVEuv//LZtyi6D/hvOF8WDJiyYNTsboBXWIYuuCnyNCXcAZmtWjpduuDBPIDGsGS -7qlATnPCIBUDdZfvH8zjWO4EWTb6SNnimmdd8ZmNeG1cKa/wJ/xjeMogEz4wwp1b -lHOd7PUXjEwW08Ekagx4gmHUuh6IfIlfNbgqPnNI6uzLoa+PDrhybQmNFSmC7CN0 -GjFBQ/de5uzqrUyZ9UQHCp2jF9rHnzgukMcL/k107zPUoi7Ry9fL5FQ+zPljn/Ap -JcVQ9zFEJP9z/smLPgFqCM3RgIGQtAkwqTS+dy2BabN7PugKhXbE+lPLPdD1iaPe -iIGbyx3au1+T4TR8EPSo928vMSLA2L1aZ8BeMQS6WwcIOJ5T/PRrvZRkJswCyO3U -ptgMlkrwRqHDvpy+uywIpg== ------END CERTIFICATE----- diff --git a/docker/nginx/test.crt b/docker/nginx/test.crt deleted file mode 100644 index 6fc0b57a7..000000000 --- a/docker/nginx/test.crt +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFQjCCAyoCCQCb5O/0dce0ojANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJD -SDEKMAgGA1UECAwBLjEKMAgGA1UEBwwBLjEKMAgGA1UECgwBLjEKMAgGA1UECwwB -LjESMBAGA1UEAwwJbG9jYWxob3N0MRAwDgYJKoZIhvcNAQkBFgEuMB4XDTE5MDQw -NDE0MzkxOFoXDTIwMDQwMzE0MzkxOFowYzELMAkGA1UEBhMCQ0gxCjAIBgNVBAgM -AS4xCjAIBgNVBAcMAS4xCjAIBgNVBAoMAS4xCjAIBgNVBAsMAS4xEjAQBgNVBAMM -CWxvY2FsaG9zdDEQMA4GCSqGSIb3DQEJARYBLjCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAMdOkUXeK1UPD+eyvfJTCCu09U09DeRH0xHso2ZXHzNHAulc -TF8Yx9kkTC6IHTSZX9Of77k/SoH3V2Tii1fBa06N3Xa8PAZ8pBxPsxiH8czdabBU -WYxgZ/n8Y/K5DvJRyspZB/2SxGMRXnRVnUgqXti3MFfHvU+Z9PXcgBOPi0jRfxqO -EznJjyXoLPY1Valdqbr3Ew97tpksxKDXCoKHyq0GoGQZC2iYMl8pxEzVYnGZ3IYY -65bX6/49SuVxU1ECd79o7ozrHiVMNDQRfeQDJknG2HOmDiDvgU/MgCkJhAtg7Oz0 -Z1x7rSlY7zFBDmalDNzmRZ6dKZcvdjJv+JDPZsnOrvPc9fMOzAPHxKSNsQm9+5/I -bhj6iHLntb9cqhsB1sUgX+OcjERZihJDrgIym7cKm1SLOIyn7SUQfuPKRvsA9GOc -NmhMV+uIxFsDXK6yxdplSKvYXRsqGr9oD1txBjKZHAIqHM/2/UWwXgZr98t2iz9G -EZX4PXZVgokrDYryFJIEFttvRdeX4uqy+0kMTzW21ju1BqXIjFHTvR4dXaLgCBZW -cFCinWGp+G5d3b8w/myGYDQNDP7RjzDbV7yJKPMMPdq9/LehoTz9WUWetsPgj+Ye -aar15qanLDuZqUr0gmGMKlj05XwgE1fc3tvl/Nxv7nOmXld5agjfbQ75+GxvAgMB -AAEwDQYJKoZIhvcNAQELBQADggIBADG3AV9WDlZYsRdC+Le2YLwRTM2v2/mZ1Cgq -s8oktgk3upZI/6L09s2rbIHpEurEucwMxlEvZTDthKOafVYXJedLT4IFT9ezaP58 -eBLRS+LUlpKO7IHBEPe6FP5kXWiLpJlGgIMeOhJbb65x7eojbSOhATNo1vPRpg5w -xLKX+s0keAGUyuKvOkE+FSInT87zQeH29p4CqBxPZLJZfCzLb4FIgCz9FIZ7KEIW -h26QSBfboBIJ+H5RPWO8WsaH0jgFkTcmHs5oOVsZv3qQUVPJfEfyG007vcviw1ok -4uwNOv22KJDI/VBJ5UbzJOcD0BW0EMw7/1pcJ3itm17YvmAReTIOwDa0VMxGji1d -6PEni1EKiJyiKuEqluEbcWLV4A7LbqItxOobVVcZJPB/NwlV/vKwAzJJ4Q7kItSn -IpaDarV+P69gUWKGjWQCFLU3LtzUiK/jbGfAWa7V4PZ1M2q35wWQMzmj0jjidFFn -HxOc+aeq63+2TFaCh0D1XzG+dmtpPMO/nax6Ak0QSIBYjTvGjXhJM3vLIJZBdvmM -XVdZUoE6S0ha5LPK9xryKF20wIq0A/rZ/c3u2VnDPQKeMs6tCxhQcGIaGDHWSAFh -dO4oouQYaH5vDH5eESrwM22PJvjd5Lykv2l/oBFpMyrYkDh4Nqm3lqY/kB+W2U7m -ZnihdMEE ------END CERTIFICATE----- diff --git a/docker/nginx/test.key b/docker/nginx/test.key deleted file mode 100644 index f01556b83..000000000 --- a/docker/nginx/test.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -ox5oXud+Wan4s1Aw/fVhc1akHSDfH2En ------END PRIVATE KEY----- diff --git a/pyproject.toml b/pyproject.toml index d71ea1cd6..905647436 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -266,6 +266,7 @@ convention = "pep257" [tool.poe.tasks] # Setup bootstrap = {cmd = "./scripts/bootstrap", help = "Runs bootstrap"} +certs = {cmd = "./scripts/gen-certs", help = "Generates the development certificates"} console = {cmd = "./scripts/console", help = "Opens invenio shell"} server = {cmd = "./scripts/server", help = "Starts the server"} setup = {cmd = "./scripts/setup", help = "Runs setup"} diff --git a/scripts/bootstrap b/scripts/bootstrap index 0610ca2e3..a5725a5c4 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -50,6 +50,7 @@ deploy=false cmd="uv sync --frozen" ci=false tgz_file="" +force_certs=false # uv is a mandatory condition to launch this program! if [[ -z "${VIRTUAL_ENV}" ]]; then @@ -73,6 +74,9 @@ while test $# -gt 0; do --deploy) deploy=true ;; + --force-certs) + force_certs=true + ;; *) error_msg "Option $1 not recognized" ;; esac shift @@ -132,6 +136,16 @@ else fi +# Generate the certificates used by the HTTPS development server. Skipped on +# deploy, where the application image has no use for a development private key. +if ! $deploy ; then + if $force_certs ; then + $(dirname "$0")/gen-certs --force + else + $(dirname "$0")/gen-certs + fi +fi + # Compile translations catalogs info_msg "Compile translations catalogs" uv run pybabel compile -d sonar/translations diff --git a/scripts/gen-certs b/scripts/gen-certs new file mode 100755 index 000000000..cd60c30a8 --- /dev/null +++ b/scripts/gen-certs @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Fondation RERO+ +# SPDX-License-Identifier: AGPL-3.0-or-later + +source $(dirname "$0")/functions + +set -e + +PROGRAM=`basename $0` +CERTS_DIR="$(dirname "$0")/../.certs" +CERT="${CERTS_DIR}/dev.crt" +KEY="${CERTS_DIR}/dev.key" +HOSTS=(sonar.ch localhost 127.0.0.1 ::1) +force=false + +while test $# -gt 0; do + case "$1" in + -f|--force) + force=true ;; + *) message "Option $1 not recognized" "error" ;; + esac + shift +done + +section "Generate development certificates" "info" + +if [[ -f "${CERT}" && -f "${KEY}" ]] && ! $force ; then + message "Certificates already exist, skipping. Use --force to regenerate." "info" + exit 0 +fi + +mkdir -p "${CERTS_DIR}" + +if command -v mkcert &> /dev/null; then + # mkcert signs with a local CA, so browsers accept the certificate without + # warning. This matters for SAML: the identity provider answers with an + # auto-submitted POST, and clicking through a browser interstitial loses it. + message "Generating with mkcert" "info" + mkcert -cert-file "${CERT}" -key-file "${KEY}" "${HOSTS[@]}" + message "Run 'mkcert -install' once if the certificate is not trusted yet." "info" +else + # Fallback used by the CI and any environment without mkcert. The browser + # will warn about the unknown issuer. + message "mkcert not found, generating a self-signed certificate with openssl" "info" + openssl req -x509 -newkey rsa:4096 -sha256 -days 825 -nodes \ + -keyout "${KEY}" -out "${CERT}" \ + -subj "/CN=sonar.ch" \ + -addext "subjectAltName=DNS:sonar.ch,DNS:localhost,IP:127.0.0.1,IP:::1" +fi + +chmod 600 "${KEY}" + +message "Certificates generated in ${CERTS_DIR}" "success" diff --git a/scripts/server b/scripts/server index be6531ef9..efb446304 100755 --- a/scripts/server +++ b/scripts/server @@ -45,10 +45,20 @@ if [[ -z "${FLASK_DEBUG}" ]]; then export FLASK_DEBUG=True fi +cert="$script_path"/../.certs/dev.crt +key="$script_path"/../.certs/dev.key + +# Checked before anything is started, so that a missing certificate does not +# leave a celery worker behind. +if [[ ! -f "${cert}" || ! -f "${key}" ]]; then + message "Development certificates not found, run: uv run poe certs" "error" + exit 1 +fi + # Start Worker and Beat if $worker; then section "Start celery worker" "info" - celery --app sonar.celery worker --loglevel ${CELERY_LOG_LEVEL} --beat & PID_CELERY=$! + celery --app sonar.celery worker --loglevel ${CELERY_LOG_LEVEL} --beat & pid_celery=$! message "Done" "success" fi @@ -56,8 +66,8 @@ fi # Start web server section "Start web server" "info" invenio run \ - --cert "$script_path"/../docker/nginx/test.crt \ - --key "$script_path"/../docker/nginx/test.key \ + --cert "${cert}" \ + --key "${key}" \ -p $PORT & pid_server=$! message "Done" "success" diff --git a/sonar/modules/shibboleth_authenticator/auth.py b/sonar/modules/shibboleth_authenticator/auth.py index eefac0d3b..411ee4dfa 100644 --- a/sonar/modules/shibboleth_authenticator/auth.py +++ b/sonar/modules/shibboleth_authenticator/auth.py @@ -42,7 +42,9 @@ def init_saml_auth(req, remote_app): with open(sp_config["private_key"]) as content_file: private_key = content_file.read() - with open(f"./data/idp_certificates/{remote_app}.crt") as content_file: + idp_certificates_path = current_app.config.get("SHIBBOLETH_IDENTITY_PROVIDERS_CERTIFICATES_PATH") + + with open(f"{idp_certificates_path}/{remote_app}.crt") as content_file: idp_cert = content_file.read() # Create auth object with settings below diff --git a/sonar/modules/shibboleth_authenticator/config.py b/sonar/modules/shibboleth_authenticator/config.py index 6f5ef9cda..506585a93 100644 --- a/sonar/modules/shibboleth_authenticator/config.py +++ b/sonar/modules/shibboleth_authenticator/config.py @@ -14,11 +14,20 @@ ) """ -SHIBBOLETH_SERVICE_PROVIDER_CERTIFICATE = "./docker/nginx/sp.pem" -"""Path to certificate.""" +SHIBBOLETH_SERVICE_PROVIDER_CERTIFICATE = None +"""Path to the service provider certificate. -SHIBBOLETH_SERVICE_PROVIDER_PRIVATE_KEY = "./docker/nginx/sp.key" -"""Path to certificate private key.""" +Left undefined on purpose: the certificate identifies the service provider to +the identity providers, so it must be registered in the SWITCH edu-ID resource +registry. Each environment provides its own pair, outside of this repository. +SAML authentication stays disabled until both this and the private key are set. +""" + +SHIBBOLETH_SERVICE_PROVIDER_PRIVATE_KEY = None +"""Path to the service provider private key.""" + +SHIBBOLETH_IDENTITY_PROVIDERS_CERTIFICATES_PATH = "./data/idp_certificates" +"""Directory storing the identity providers certificates, named .crt.""" SHIBBOLETH_SERVICE_PROVIDER = {} """Configuration of service provider.""" diff --git a/sonar/modules/shibboleth_authenticator/ext.py b/sonar/modules/shibboleth_authenticator/ext.py index 1f05edb0f..d42734bec 100644 --- a/sonar/modules/shibboleth_authenticator/ext.py +++ b/sonar/modules/shibboleth_authenticator/ext.py @@ -21,11 +21,19 @@ def init_app(self, app): def init_config(self, app): """Initialize configuration.""" - # Update service provider configuration with certificate path found - # in environment variables + # Update service provider configuration with the configured certificate + # paths. They are only injected when set, so that an unconfigured + # instance raises the explicit error from ``init_saml_auth`` instead of + # trying to open ``None``. service_provider = app.config.get("SHIBBOLETH_SERVICE_PROVIDER") - service_provider["x509cert"] = app.config.get("SHIBBOLETH_SERVICE_PROVIDER_CERTIFICATE") - service_provider["private_key"] = app.config.get("SHIBBOLETH_SERVICE_PROVIDER_PRIVATE_KEY") + certificate = app.config.get("SHIBBOLETH_SERVICE_PROVIDER_CERTIFICATE") + private_key = app.config.get("SHIBBOLETH_SERVICE_PROVIDER_PRIVATE_KEY") + + if certificate: + service_provider["x509cert"] = certificate + + if private_key: + service_provider["private_key"] = private_key app.config.setdefault("SHIBBOLETH_SERVICE_PROVIDER", service_provider) diff --git a/sonar/modules/utils.py b/sonar/modules/utils.py index 35363af4d..cb8c96d47 100644 --- a/sonar/modules/utils.py +++ b/sonar/modules/utils.py @@ -85,6 +85,12 @@ def send_email(recipients, subject, template, ctx=None, html=True, lang="en"): def get_switch_aai_providers(): """Return the list of available SWITCHaai providers.""" + # Without a service provider key pair no SAML request can be signed, so no + # provider is offered rather than letting the user reach a dead end. + service_provider = current_app.config.get("SHIBBOLETH_SERVICE_PROVIDER", {}) + if not service_provider.get("x509cert") or not service_provider.get("private_key"): + return [] + providers = [] for provider, data in current_app.config.get("SHIBBOLETH_IDENTITY_PROVIDERS").items(): # Don't take providers flagged as dev in production mode diff --git a/sonar/theme/templates/sonar/accounts/login.html b/sonar/theme/templates/sonar/accounts/login.html index deec96063..e3adc07f3 100644 --- a/sonar/theme/templates/sonar/accounts/login.html +++ b/sonar/theme/templates/sonar/accounts/login.html @@ -28,11 +28,13 @@

{{ _('Log in to account') }}

ORCID + {% if aai_providers() %}
{% with label = _('Log in with SWITCHaai') %} {% include 'sonar/partial/switch_aai_dropdown.html' %} {% endwith %}
+ {% endif %} diff --git a/sonar/theme/templates/sonar/accounts/signup.html b/sonar/theme/templates/sonar/accounts/signup.html index 88348df0c..24091faa6 100644 --- a/sonar/theme/templates/sonar/accounts/signup.html +++ b/sonar/theme/templates/sonar/accounts/signup.html @@ -37,9 +37,11 @@

ORCID + {% if aai_providers() %}
{% include 'sonar/partial/switch_aai_dropdown.html' %}
+ {% endif %} diff --git a/tests/conftest.py b/tests/conftest.py index a1ffb080e..181689cf7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ import shutil import sys import tempfile -from datetime import date +from datetime import UTC, date, datetime, timedelta from io import BytesIO from os.path import dirname, join from types import SimpleNamespace @@ -16,6 +16,10 @@ import pytest import requests_mock +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509.oid import NameOID from dotenv import load_dotenv from flask_principal import ActionNeed from flask_security.utils import hash_password @@ -135,8 +139,55 @@ def instance_path(): shutil.rmtree(path) +@pytest.fixture(scope="session") +def saml_certificates(tmp_path_factory): + """Generate the SAML certificates needed by the tests. + + Certificates are only read and formatted, never used to validate a real + signature, so ephemeral ones are enough and no key material has to be + versioned. + """ + path = tmp_path_factory.mktemp("saml") + + def create_certificate(common_name): + """Create a self signed certificate and its private key.""" + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, common_name)]) + certificate = ( + x509.CertificateBuilder() + .subject_name(name) + .issuer_name(name) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(datetime.now(UTC)) + .not_valid_after(datetime.now(UTC) + timedelta(days=1)) + .sign(key, hashes.SHA256()) + ) + return key, certificate + + key, certificate = create_certificate("sonar.ch") + (path / "sp.pem").write_bytes(certificate.public_bytes(serialization.Encoding.PEM)) + (path / "sp.key").write_bytes( + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + ) + ) + + # Named after the "idp" identity provider key configured below. + _, idp_certificate = create_certificate("idp.com") + (path / "idp.crt").write_bytes(idp_certificate.public_bytes(serialization.Encoding.PEM)) + + return SimpleNamespace( + path=str(path), + certificate=str(path / "sp.pem"), + private_key=str(path / "sp.key"), + ) + + @pytest.fixture(scope="module", autouse=True) -def app_config(app_config): +def app_config(app_config, saml_certificates): """Define configuration for module.""" help_test_dir = join(dirname(__file__), "data", "help") @@ -144,9 +195,13 @@ def app_config(app_config): "strict": True, "debug": True, "entity_id": "entity_id", - "x509cert": "./docker/nginx/sp.pem", - "private_key": "./docker/nginx/sp.key", + "x509cert": saml_certificates.certificate, + "private_key": saml_certificates.private_key, } + # The extension overwrites the two keys above with these values. + app_config["SHIBBOLETH_SERVICE_PROVIDER_CERTIFICATE"] = saml_certificates.certificate + app_config["SHIBBOLETH_SERVICE_PROVIDER_PRIVATE_KEY"] = saml_certificates.private_key + app_config["SHIBBOLETH_IDENTITY_PROVIDERS_CERTIFICATES_PATH"] = saml_certificates.path app_config["SHIBBOLETH_IDENTITY_PROVIDERS"] = { "idp": { diff --git a/tests/ui/shibboleth_authenticator/conftest.py b/tests/ui/shibboleth_authenticator/conftest.py index 9ef53ab57..0c105d9e5 100644 --- a/tests/ui/shibboleth_authenticator/conftest.py +++ b/tests/ui/shibboleth_authenticator/conftest.py @@ -17,12 +17,12 @@ def valid_attributes(): @pytest.fixture(scope="module") -def valid_sp_configuration(): +def valid_sp_configuration(saml_certificates): """Fixture for valid service provider configuration.""" return { "strict": True, "debug": True, "entity_id": "entity_id", - "x509cert": "./docker/nginx/sp.pem", - "private_key": "./docker/nginx/sp.key", + "x509cert": saml_certificates.certificate, + "private_key": saml_certificates.private_key, } diff --git a/tests/ui/shibboleth_authenticator/test_shibboleth_auth.py b/tests/ui/shibboleth_authenticator/test_shibboleth_auth.py index 3bef4a940..d9f2545ea 100644 --- a/tests/ui/shibboleth_authenticator/test_shibboleth_auth.py +++ b/tests/ui/shibboleth_authenticator/test_shibboleth_auth.py @@ -20,29 +20,36 @@ def test_get_identity_provider_configuration(app): assert auth.get_identity_provider_configuration("idp").get("entity_id") == "https://idp.com/shibboleth" -def test_init_saml_auth(app, request): +def test_init_saml_auth(app, request, saml_certificates): """Test SAML auth initialization.""" - # Valid init without strict mode + certificate = saml_certificates.certificate + private_key = saml_certificates.private_key + + # Strict mode defaults to True when not configured app.config.update( SHIBBOLETH_SERVICE_PROVIDER={ "debug": True, "entity_id": "entity_id", - "x509cert": "./docker/nginx/sp.pem", - "private_key": "./docker/nginx/sp.key", + "x509cert": certificate, + "private_key": private_key, } ) - assert isinstance(auth.init_saml_auth(request, "idp"), OneLogin_Saml2_Auth) + saml_auth = auth.init_saml_auth(request, "idp") + assert isinstance(saml_auth, OneLogin_Saml2_Auth) + assert saml_auth.get_settings().is_strict() - # Valid init without debug + # Debug mode defaults to False when not configured app.config.update( SHIBBOLETH_SERVICE_PROVIDER={ "strict": True, "entity_id": "entity_id", - "x509cert": "./docker/nginx/sp.pem", - "private_key": "./docker/nginx/sp.key", + "x509cert": certificate, + "private_key": private_key, } ) - assert isinstance(auth.init_saml_auth(request, "idp"), OneLogin_Saml2_Auth) + saml_auth = auth.init_saml_auth(request, "idp") + assert isinstance(saml_auth, OneLogin_Saml2_Auth) + assert not saml_auth.get_settings().is_debug_active() # Init failed caused by certificate lack app.config.update( @@ -50,7 +57,7 @@ def test_init_saml_auth(app, request): "debug": True, "strict": True, "entity_id": "entity_id", - "private_key": "./docker/nginx/sp.key", + "private_key": private_key, } ) with pytest.raises(Exception) as e: @@ -63,7 +70,7 @@ def test_init_saml_auth(app, request): "debug": True, "strict": True, "entity_id": "entity_id", - "x509cert": "./docker/nginx/sp.pem", + "x509cert": certificate, } ) with pytest.raises(Exception) as e: @@ -75,8 +82,8 @@ def test_init_saml_auth(app, request): SHIBBOLETH_SERVICE_PROVIDER={ "debug": True, "strict": True, - "x509cert": "./docker/nginx/sp.pem", - "private_key": "./docker/nginx/sp.key", + "x509cert": certificate, + "private_key": private_key, } ) with pytest.raises(Exception) as e: diff --git a/tests/ui/test_utils.py b/tests/ui/test_utils.py index 3a837202a..7a97e3819 100644 --- a/tests/ui/test_utils.py +++ b/tests/ui/test_utils.py @@ -70,6 +70,10 @@ def test_get_switch_aai_providers(app): app.config["SHIBBOLETH_IDENTITY_PROVIDERS"] = {"idp": {}, "idpdev": {"dev": True}} assert get_switch_aai_providers() == ["idp"] + # No provider is offered when the service provider key pair is missing + app.config["SHIBBOLETH_SERVICE_PROVIDER"] = {"entity_id": "entity_id"} + assert get_switch_aai_providers() == [] + def test_get_current_language(app): """Test getting the current language.""" diff --git a/tests/ui/test_views.py b/tests/ui/test_views.py index dbacd2f30..658e54705 100644 --- a/tests/ui/test_views.py +++ b/tests/ui/test_views.py @@ -256,6 +256,15 @@ def test_schema_deposits(client, moderator, submitter, moderator_dedicated): assert "subdivisions" in res.json["schema"]["properties"]["diffusion"]["propertiesOrder"] +def test_login_switch_aai_button(app, client): + """Test the SWITCHaai button is only displayed when SAML is configured.""" + assert b"SWITCHaai" in client.get(url_for("security.login")).data + + # Without a service provider key pair no provider can be offered + app.config["SHIBBOLETH_SERVICE_PROVIDER"] = {"entity_id": "entity_id"} + assert b"SWITCHaai" not in client.get(url_for("security.login")).data + + def test_profile(client, user): """Test profile page.""" # Not logged