Aller au contenu

Stirling-PDF

traefik-authelia-stirlingPDF-logos

👋 Présentation

Le logiciel Stirling PDF est accessible via votre navigateur web et présente les fonctionnalités suivantes (liste non exhaustive) :

  • Interface graphique interactive complète pour fusionner/diviser/faire pivoter/déplacer des pages et des PDF complets.
  • Possibilité de diviser les PDF en plusieurs fichiers à partir des numéros de page spécifiés ou d’extraire toutes les pages en tant que fichiers individuels.
  • Fusionner plusieurs PDF ensemble en un seul fichier.
  • Ajouter/Générer des signatures.
  • Auto Split PDF (Avec séparateurs de page numérisés physiquement)
  • Supprimer les pages vierges.
  • Convertir PDF en Word/Powerpoint/Autres (à partir de fichiers non scannés, tout comme les logiciels payant du commerce)
  • Reconnaissance optique des caractères avec l’OCR (Optical Character Recognition).
  • Authentification possible intégrée ou en mode SSO.
  • API

Liste complète des fonctionnalités

L'application est sous licence GPL-3.0.

J'installerai le logciciel sur Docker Swarm, mais il sera relativement facile de le basculer sur Docker.

Les briques logicielles suivantes seront utilisées :

  • Traefik comme reverse-proxy avec le middleware Crowdsec.
  • Authelia comme solution IAM qui permettra de gérer l'accès et l'authentification sur Stirling-PDF.

Il faudra également déclarer chez votre registrar le nom de domaine utilisé pour l'application.

Introduction au protocole OIDC

OIDC a été développé par l’OpenID Foundation, qui comprend des sociétés comme Google et Microsoft. Alors qu’OAuth 2.0 est un protocole d'autorisation, OIDC est un protocole d'authentification d'identité qui sert à vérifier l'identité d'un utilisateur d'un service client. Il permet d’ajouter une surcouche d’authentification à OAuth 2.0, tout en donnant la possibilité de faire de la fédération d’identité. Cela permet notamment d’offrir à l’utilisateur la possibilité d’utiliser plusieurs services, en se connectant une seule fois auprès d’un tiers de confiance, grâce au SSO.

Voici les étapes du processus OIDC :

  1. L'utilisateur accède à l'application cliente : L'utilisateur tente de se connecter à une application cliente (Client).
  2. Redirection vers le fournisseur d'identité (IdP) : Le client redirige l'utilisateur vers le fournisseur d'identité (IdP) avec une demande d'authentification.
  3. Authentification de l'utilisateur : L'utilisateur se connecte auprès du fournisseur d'identité (IdP), généralement en saisissant ses identifiants.
  4. Consentement de l'utilisateur : L'IdP peut demander à l'utilisateur de consentir à partager certaines informations avec le client.
  5. Le fournisseur d'identité émet des tokens : - Après authentification et consentement, l'IdP redirige l'utilisateur vers le client avec un code d'autorisation. - Le client échange ce code d'autorisation contre des tokens (ID token, access token et potentiellement un refresh token) auprès de l'IdP.
  6. Le client utilise les tokens : - Le client utilise l'ID token pour obtenir des informations sur l'utilisateur (claims). - L'access token peut être utilisé pour accéder aux ressources protégées par des API.

Configuration d'Authelia

Génération des secrets

Nous utiliserons la stack Authelia/LLDAP pour authentifier les utilisateurs souhaitant utiliser Stirling-PDF. Cette stack est documenté ici : Stack AUthelia/LLDAP

Rappel de l'arborescence de la stack Authelia/LLDAP
olivier@ds06:/mnt/nfsdatas/stack-sso$ tree -L 3
.
├── config
   ├── authelia
      └── configuration.yml
   └── lldap
       ├── lldap_config.toml
       ├── private_key
       ├── ssl
       └── users.db
├── data
   ├── authelia
      └── db.sqlite3
   └── redis
       └── dump.rdb
├── docker-stack.yml
├── env
   ├── container-vars-authelia.env
   └── container-vars-lldap.env
├── log
   └── authelia
       └── authelia.log
└── secrets
    ├── authelia
       ├── AUTHENTICATION_BACKEND_LDAP_PASSWORD
       ├── JWT_SECRET
       ├── NOTIFIER_SMTP_PASSWORD
       ├── SESSION_SECRET
       └── STORAGE_ENCRYPTION_KEY
    └── lldap
        ├── JWT_SECRET
        └── LDAP_USER_PASS

Avant de configurer le provider et le client OIDC, nous allons renseigner les secrets afin de protéger les informations sensibles.

Pour commencer il faut créer le secret HMAC.

HMAC et JWT

Le HMAC dans OIDC est utilisé pour signer les tokens JWT, assurant ainsi l'intégrité et l'authenticité des messages. C'est une méthode efficace et sécurisée lorsqu'une clé secrète partagée peut être maintenue en toute sécurité entre les parties impliquées. Le JWT est un élément clé d'OpenID Connect, utilisé pour transmettre de manière sécurisée et compacte des informations sur l'utilisateur et l'authentification.

Génération du secret HMAC
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker run authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric
Random Value: R9A77AAq1G0Nz85dWQedoCEHSY98pvqGnq0vuMOhAYu0gnQa9adaazOHIW0K0mJP
# Copier/Coller la valeur dans secrets/authelia/IDENTITY_PROVIDERS_OIDC_HMAC_SECRET

Il faut ensuite générer le ClientSecret. Le Client Secret est une sorte de mot de passe applicatif. Conservez la sortie des commandes suivantes.

Génération du client ID et du ClientSecret
# ClientID
olivier@ds06:/mnt/nfsdatas/stirling-pdf$ docker run authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric
Random Value: mED8rrPupGTtrKEVcbNlRPxkNOnNTUHelFtZQuiFHRQgxDchfGy3Nn0SRmPeSGuv

# ClientSecret
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker run authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986
# Affichage du mot de passe en clair que nous renseignerons dans la configuration OIDC de Stirling-PDF
Random Password: buHsi7V0xU_rRuKlJGCcpUAgeVi79GEbzMax~qCfi70_BhRubUrQOmbNDRH2_rqJPSWWojUc
# Affichage du mot de passe chiffré que nous renseignerons dans la configuration OIDC sur Authelia
Digest: $pbkdf2-sha512$310000$Z5Tz88B7XZKZEzgT..v6pw$aTu9p/.hfrhf/v0Z.fAu9zH4KTKzJCwheIMDr0uY5jhcHymDyYCFKMFoNJ.5Qj7fURGoenIhog1WPrI6ddpW7w

Enfin il faut générer un certificat auto-signé qui sera utilisé par JWKS.

JWKS

Le JWKS est essentiel pour la sécurité dans les systèmes OIDC et JWT. Il permet aux clients de vérifier l'authenticité et l'intégrité des tokens en fournissant un moyen standardisé de publier et de récupérer les clés publiques utilisées pour la signature des tokens. Lorsqu'un client reçoit un JWT, il doit vérifier la signature du token pour s'assurer qu'il a été émis par un fournisseur de confiance et qu'il n'a pas été altéré. Le client récupère le document JWKS du fournisseur d'identité et utilise la clé publique appropriée pour vérifier la signature du JWT.

Génération du certificat auto-signé
olivier@ds06:/mnt/nfsdatas/stack-sso$ cd secrets/authelia
olivier@ds06:/mnt/nfsdatas/stack-sso/secrets/authelia$ docker run -u "$(id -u):$(id -g)" -v "$(pwd)":/keys authelia/authelia:latest authelia crypto certificate rsa -b 3072 generate --common-name authelia.idp --directory /keys
Generating Certificate

    Serial: 53d1088937434bccd33159d98245a6c9

Signed By:
    Self-Signed

Subject:
    Common Name: authelia.idp, Organization: [Authelia], Organizational Unit: []
    Country: [], Province: [], Street Address: [], Postal Code: [], Locality: []

Properties:
    Not Before: 2024-06-22T13:57:14Z, Not After: 2025-06-22T13:57:14Z
    CA: false, CSR: false, Signature Algorithm: SHA256-RSA, Public Key Algorithm: RSA, Bits: 3072
    Subject Alternative Names: 

Output Paths:
    Directory: /keys
    Private Key: private.pem
    Certificate: public.crt

Configuration du provider OIDC

Configuration du fichier de configuration d'Authelia
# À ajouter à la fin du fichier config/authelia/configuration.yml
(...)
identity_providers:
  oidc:
    jwks:
      - key: {{ secret "/secrets/private.pem" | mindent 10 "|" | msquote }}
        certificate_chain: |
          -----BEGIN CERTIFICATE-----
          MIIEEzCCAnugAwIBAgIQU9EIiTdDS8zTMVnZgkWmyTANBgkqhkiG9w0BAQsFADAq
          MREwDwYDVQQKEwhBdXRoZWxpYTEVMBMGA1UEAxMMYXV0aGVsaWEuaWRwMB4XDTI0
          MDYyMjEzNTcxNFoXDTI1MDYyMjEzNTcxNFowKjERMA8GA1UEChMIQXV0aGVsaWEx
          FTATBgNVBAMTDGF1JHgsdnbjyGFDCxchgfdSxhfJKoZIhvcNAQEBBQADggGPADCC
          AYoCggGBAKyI1TciWM2qMjp2ahkeRhV8rmPIUfG0wpn9HPkqlHBc052yK/C2vd1G
          a/yQf7KWA16uiDasEynk3mvX6wGtNvwX4qe1t91Zgk/pC1NDl7zzUIgnAs7yok38
          L3XCqnYrmJjPqj9ACfBLv/2q/D7Z7ft79CEADWUCf74hY/Ksy5XN3Doju1Ye2RyR
          zCGn0fEE944juHRO4U8GHjIeoHuq8Qvm+t7RJzVG4BmZcvOA/VY+CWmeB7v1Q2mK
          VE5XklZJpSVjv392H+n4hzbAILW0dP499mOiGDXU8nlXz1UxQfG6eiqyVDKCXZ8v
          7a7epQL3yT3HZOE8d2SmbmlksdfoijHUGf+PLlx7MaCqcnb6doqWWu7RuIT1c141
          vCuyC3mhktGRmEldh0y0wnopy9wDsGP4ska4jJX5oNOEQaP9+HpF364TgC0cUvOs
          0EflN0+ERlup/gbXULVLH5bu3VwdLEzG9UXHtm9G9FWEa9f2/1KqbJ+v77wrKjdj
          mUxgtsy8MwIDAQABozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYB
          BQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAYEAJH8CjMw8AkR+
          bCqu7I9xOF97JhOcG6OyPyL5TOHEtHtL3rOZ/0xwEp8ceC9R3vYMB/vH91OawJte
          lEQhVpTSPHZLTE/9Fo7dz4PL0TuzFpXlMC36ujOrGRQHOCLsaEoTN372d7qKeu7e
          yFtfVtqHVZMN/cBALspSK/oz5v6EeRBWC7mObBUEOGKIp1JopEErR6TGM5d4e6di
          XfdAcMAxyJ3qhCx+4o4VxQUYaTUlzAjHdnDwU590znk9FY16kcS4ibJ13B/lqCxY
          yXGuirnJTdidHLKUIOUnshdsy+oohJHGHGFuTdHTsAZErsfOfYaYU+WuhPRIDT6z
          0q344WkNwvDYnnth09q4K3W9Bjjpm1b8AtLZl2Q4TNtDMs6phKUtZMdNVSRgRvxb
          oNcSObLjXwf7HSSvcFWKpVLKYVCK8ArIWNLKkeIIDuYWdzBmb01LWi0Jdo6HKPtR
          +8S8dTf2dc/ehNjE4MxzuUItBmsGFk/3R24OKf5qajPAd1lFP6MK
          -----END CERTIFICATE-----
    lifespans.access_token: 1h
    lifespans.authorize_code: 1m
    lifespans.id_token: 1h
    lifespans.refresh_token: 90m
    enable_client_debug_messages: false
    clients:
      - client_id: 'mED8rrPupGTtrKEVcbNlRPxkNOnNTUHelFtZQuiFHRQgxDchfGy3Nn0SRmPeSGuv'
        client_name: 'Stirling-PDF'
        client_secret: '$pbkdf2-sha512$310000$Z5Tz88B7XZKZEzgT..v6pw$aTu9p/.hfrhf/v0Z.fAu9zH4KTKzJCwheIMDr0uY5jhcHymDyYCFKMFoNJ.5Qj7fURGoenIhog1WPrI6ddpW7w'
        public: false
        authorization_policy: 'one_factor'
        redirect_uris:
          - 'https://pdf.dev.quercylibre.fr/login/oauth2/code/oidc'
        scopes:
          - 'openid'
          - 'email'
          - 'profile'
        requested_audience_mode: 'explicit'
        consent_mode: 'auto'
        pre_configured_consent_duration: '1y'

Il faut déclarer la variable HMAC.

Configuration de la variable dans le fichier env/container-vars-authelia.env
# Ajouter ces 2 lignes dans le fichier
(...)
X_AUTHELIA_CONFIG_FILTERS=template # Important pour importer la clé privée générée précédemment secrets/private.pem
AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE=/secrets/IDENTITY_PROVIDERS_OIDC_HMAC_SECRET

Il ne reste plus qu'à relancer la stack sso et vérifier dans les logs que tout est ok.

olivier@ds06:/mnt/nfsdatas/stack-sso$ docker stack rm sso
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker stack deploy -c docker-stack.yml -d sso
# Il faut patienter environ 1 minute le temps que LLDAP soit lancé
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker service logs sso_authelia -f
Arborescence finale de la stack Authelia/LLDAP
olivier@ds06:/mnt/nfsdatas/stack-sso$ tree -L 3
.
├── config
   ├── authelia
      └── configuration.yml
   └── lldap
       ├── lldap_config.toml
       ├── private_key
       ├── ssl
       └── users.db
├── data
   ├── authelia
      └── db.sqlite3
   └── redis
       └── dump.rdb
├── docker-stack.yml
├── env
   ├── container-vars-authelia.env
   └── container-vars-lldap.env
├── log
   └── authelia
       └── authelia.log
└── secrets
    ├── authelia
       ├── AUTHENTICATION_BACKEND_LDAP_PASSWORD
       ├── IDENTITY_PROVIDERS_OIDC_HMAC_SECRET
       ├── JWT_SECRET
       ├── NOTIFIER_SMTP_PASSWORD
       ├── private.pem
       ├── public.crt
       ├── SESSION_SECRET
       └── STORAGE_ENCRYPTION_KEY
    └── lldap
        ├── JWT_SECRET
        └── LDAP_USER_PASS

Configuration de Stirling-PDF

Création de l'arborescence
olivier@ds06:/mnt/nfsdatas$ mkdir -p stirling-pdf/config
olivier@ds06:/mnt/nfsdatas$ sudo chown -R olivier: stirling-pdf/config
olivier@ds06:/mnt/nfsdatas$ cd stirling-pdf

Il faut créer le fichier de configuration du service.

docker-stack.yml
services:
  stirling-pdf:
    image: frooodle/s-pdf:latest
    networks:
      - web
    volumes:
      - ./config:/configs:rw
    # TODO : tester la reconnaissance OCR sur RPI
    #   -  /usr/share/tesseract-ocr/4.00/tessdata:/usr/share/tesseract-ocr/4.00/tessdata
    environment:
      DOCKER_ENABLE_SECURITY: "true"
      SECURITY_ENABLELOGIN: "true"
      SECURITY_OAUTH2_ENABLED: "true"
      SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF
      SECURITY_OAUTH2_ISSUER: "https://a.dev.quercylibre.fr"
      SECURITY_OAUTH2_CLIENTID: "mED8rrPupGTtrKEVcbNlRPxkNOnNTUHelFtZQuiFHRQgxDchfGy3Nn0SRmPeSGuv" # Client ID from your provider
      # Pas de possibilité d'utiliser Docker Secrets. Le ClientSecret est tout de même masqué dans le logs.
      SECURITY_OAUTH2_CLIENTSECRET: "buHsi7V0xU_rRuKlJGCcpUAgeVi79GEbzMax~qCfi70_BhRubUrQOmbNDRH2_rqJPSWWojUc"  # Client Secret from your provider
      SECURITY_OAUTH2_SCOPES: "openid,profile,email" # Expected OAuth2 Scope
      SECURITY_OAUTH2_USEASUSERNAME: "preferred_username" # Utilisation du username. Par défaut, c'est l'email. L'attribut "name" pour le "display name" génère un bug.
      SECURITY_OAUTH2_PROVIDER: "authelia" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
      PUID: 1001
      PGID: 1001
      SYSTEM_MAXFILESIZE: "50"
      SYSTEM_DEFAULTLOCALE: fr-FR
      LANGS: fr-FR
      UI_APPNAME: Boite à outils PDF
      UI_HOMEDESCRIPTION: Votre guichet unique auto-hébergé pour travailler sur vos PDF.
      UI_APPNAVBARNAME: Boite à outils PDF
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
      resources:
        limits:
          memory: 2G
      placement:
         constraints: 
            - node.hostname == ds06 # C'est le RPI 4.
      labels:
        - "traefik.enable=true"
        - "traefik.docker.network=web"
        - "traefik.http.routers.pdf.rule=Host(`pdf.dev.quercylibre.fr`)"
        - "traefik.http.routers.pdf.tls=true"
        - "traefik.http.routers.pdf.tls.certresolver=letsencrypt"
        - "traefik.http.routers.pdf.middlewares=crowdsec@file"
        - "traefik.http.services.pdf-service.loadbalancer.server.port=8080"
        - "diun.enable=true"
networks:
  web:
    external: true

On lance le service.

olivier@ds06:/mnt/nfsdatas/stirling-pdf$ docker stack deploy -c docker-stack.yml -d pdf
# Suivi du chargement du service
olivier@ds06:/mnt/nfsdatas/stirling-pdf$ docker service logs pdf_stirling-pdf -f

🚀 Connexion à Stirling-PDF

Connectez-vous sur l'url déclarée puis cliquez sur "Se connecter via l'authentification unique"

Portail d'authentification PDF-Stirling

Cliquez sur Authelia.

Lien vers Authelia

Authentifiez-vous sur Authelia.

Authentification Authelia

Cochez la case "Se souvenir du consentement" puis cliquez sur Accepter.

Consentement

Vous êtes alors redirigé vers Stirling-PDF

Portail Stirling-PDF

Cliquez sur l'engrenage en haut à droite. Depuis ce panneau vous pouvez vous déconnecter ou bien accéder aux paramètres du compte.

Paramètre du compte

Si vous cliquez sur "Paramètres du compte", vous avez alors accès à l'option pour paramétrer la clé API.

Paramètre du compte

🏁 Conclusion

J'ai testé quelques fonctionnalités comme l'ajout d'un filigrane, la division d'un pdf en plusieurs PDF... Et ça fonctionne 🤩

Au niveau de la prise en charge du SSO, il y a quelques bugs comme la déconnexion ou la non prise en compte du Display Name.