Skip to content

Instantly share code, notes, and snippets.

@webaxones
Last active April 22, 2025 06:58
Show Gist options
  • Save webaxones/54a9aee13bd9152e900ef30a0fcef3ed to your computer and use it in GitHub Desktop.
Save webaxones/54a9aee13bd9152e900ef30a0fcef3ed to your computer and use it in GitHub Desktop.
GitHub workflow to build a WordPress Bedrock site and deploy it to a shared server using SSH
# This GitHub workflow will build a WordPress Bedrock site and deploy it to a shared server (french one: O2Switch, but URLs can be adapted) using SSH
# Actions secrets are used to store sensitive information:
# - SSH_PRIVATE_KEY: The private key used to authenticate with the remote server
# - REMOTE_HOST: The hostname of the remote server
# - REMOTE_USER: The username used to authenticate with the remote server
# - REMOTE_PROD_TARGET: The path on the remote server where the site will be deployed to
# - REMOTE_PREPROD_TARGET: The path on the remote server where the site will be deployed to
# - URL_ENCODED_PASSWORD: The password used to authenticate with the remote server, URL encoded (e.g. using https://www.urlencoder.org/)
# Workflow triggers on pushes to the develop and master branches:
# - On the develop branch, the site is deployed to the preprod target
# - On the master branch, the site is deployed to the prod target
name: Build and Deploy
on:
push:
branches: [ develop, master ]
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Install composer dependencies
- id: build-php
name: "Install dependencies"
uses: shivammathur/setup-php@v2
with:
php-version: 8.0
- run: composer install --prefer-dist --no-dev -o
# Get the public IP of the runner
- name: Public IP
id: ip
uses: haythem/[email protected]
# Whitelist the public IP of the runner on the remote server by adding it to the SSH whitelist and wait 65 seconds for the IP to be whitelisted
- shell: bash
run: |
curl -sX GET 'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php' | fgrep 'index.live.php' | fgrep 'index.live.php?r=remove&address=' | cut -d '"' -f 2 | while read ipToRemove
do
curl -sX GET 'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/'$ipToRemove > /dev/null 2>&1
done
curl -X POST \
-d 'whitelist[address]=${{ steps.ip.outputs.ipv4 }}' \
-d 'whitelist[port]=22' \
'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php?r=add' > /dev/null 2>&1
curl -sX GET 'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php' | fgrep -q '${{ steps.ip.outputs.ipv4 }}' && echo "IP whitelisted"
sleep 65
# Deploy the develop branch to the preprod target
- name: 'Deploy on develop branch'
if: ${{ github.ref == 'refs/heads/develop' }}
uses: easingthemes/ssh-deploy@main
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i --delete-after"
SOURCE: "./"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_PREPROD_TARGET }}
EXCLUDE: "/dist/, /node_modules/, ./auth.json"
SCRIPT_BEFORE: |
whoami
ls -al
SCRIPT_AFTER: |
whoami
ls -al
echo $RSYNC_STDOUT
# Deploy the master branch to the production target
- name: 'Deploy on master branch'
if: ${{ github.ref == 'refs/heads/master' }}
uses: easingthemes/ssh-deploy@main
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i --delete-after"
SOURCE: "./"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_PROD_TARGET }}
EXCLUDE: "/dist/, /node_modules/, ./auth.json"
SCRIPT_BEFORE: |
whoami
ls -al
SCRIPT_AFTER: |
whoami
ls -al
echo $RSYNC_STDOUT
@madrzejewski
Copy link

Petite mise à jour de l'outil d'autorisation SSH dans le cPanel.

Supposant qu'on déclare ça avant de lancer les commandes :

SERVER="le-serveur.o2switch.net"
LOGIN="toto4242"
# Le mot de passe doit être urlencodé
# Version 1, si "JQ" installé : printf %s 'le mot de passe' | jq -sRr @uri
# Version 2, si PHP installé : php -r "echo urlencode('Le mot de passe');"
# Version 3, si OK avec un site tiers : https://www.urlencoder.org/fr/
PASSWORD='Le mot de passe'
ENDPOINT='frontend/o2switch/o2switch-ssh-whitelist/index.live.php'

Lister les adresses IP en liste blanche / récupérer son IP / obtenir le count des adresses déjà en WL

curl -sX GET "https://$LOGIN:$PASSWORD@$SERVER:2083/$ENDPOINT?r=list" | jq
{
  "success": true,
  "data": {
    "ip": "10.20.0.20",
    "list": [
      {
        "address": "1.2.3.4",
        "direction": "out",
        "port": 22,
        "updateDate": "21/08/2024"
      },
      {
        "address": "1.2.3.4",
        "direction": "in",
        "port": 22,
        "updateDate": "21/08/2024"
      },
      {
        "address": "2.3.4.5",
        "direction": "out",
        "port": 22,
        "updateDate": "21/08/2024"
      },
      {
        "address": "2.3.4.5",
        "direction": "in",
        "port": 22,
        "updateDate": "21/08/2024"
      },
      {
        "address": "2.2.2.2",
        "direction": "out",
        "port": 22,
        "updateDate": "29/08/2024"
      },
      {
        "address": "2.2.2.2",
        "direction": "in",
        "port": 22,
        "updateDate": "29/08/2024"
      }
    ],
    "count": 3
  }
}

Ajouter une adresse en liste blanche

Note importante : J'ai rendu l'appel bloquant, donc ça ne rend la main que lorsque le réglage est bien effectif dans le parefeu du serveur.
Ça signifie qu'il n'est plus nécessaire d'inclure un sleep 60 après l'appel.
J'ai raccourci le délai de prise en compte également, maintenant ça prend 7-8 secondes environs (contre 1 minute dans le pire des cas auparavant).

curl -sX POST -d 'whitelist[address]=3.3.3.3' -d 'whitelist[port]=22' "https://$LOGIN:$PASSWORD@$SERVER:2083/$ENDPOINT?r=add" | jq
{
  "success": true,
  "message": "L'autorisation a été ajoutée avec succès."
}

Supprimer une adresse IP de la liste blanche

Note : penser le faire pour le in & out sur la direction sinon ça ne décrémente pas le count.

curl -sX GET "https://$LOGIN:$PASSWORD@$SERVER:2083/$ENDPOINT?r=remove&address=1.2.3.4&direction=in&port=22" | jq
{
  "success": true,
  "message": "L'autorisation a été supprimée avec succès, elle sera effective d'ici quelques minutes (~5mins)."
}

@DamChtlv
Copy link

DamChtlv commented Aug 31, 2024

Merci @madrzejewski Trop cool ! Les temps de deploy réduient de moitié ✨
Je suis revenu dessus parce que depuis quelques jours mes deploy ne passaient plus 😅

Je me demande si il est possible de ton côté de gérer 2 params dans direction, par exemple : direction=in,out ça réduirait à un seul call :)

Sinon pour ceux qui passent par là, il faut modifier cette partie / step : # Whitelist the public IP of the runner ... par celle-ci :

# Whitelist the public IP of the runner on the remote server by adding it to the SSH whitelist and wait 65 seconds for the IP to be whitelisted
- name: Whitelist IP on hosting & delete github old ones (o2switch)
  shell: bash
  run: |
    ENDPOINT='frontend/o2switch/o2switch-ssh-whitelist/index.live.php'

    echo "Get actual whitelisted IPs..."
    UNIQUE_IPS=$(curl -sX GET "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=list" | jq -r '.data.list[] | .address' | sort -u)
    for address in $UNIQUE_IPS; do
      if [[ $address == "${{ secrets.IP_TO_KEEP }}" ]]; then
          echo "Keep this IP, go to the next..."
          continue
      fi
      echo "Delete this github IP: $address (in & out)"
      curl -sX GET "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=in&port=22" | jq
      sleep 3
      curl -sX GET "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=out&port=22" | jq
      sleep 3
    done
    echo "All non-whitelisted IPs deleted!"

    echo "Attempt to whitelist IP..."
    curl -sX POST -d 'whitelist[address]=${{ steps.ip.outputs.ipv4 }}' -d 'whitelist[port]=22' "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=add" | jq

J'ai quand même ajouté des sleep 3 entre les 2 appels cURL pour supprimer l'IP avec le in & avec le out (j'ai eu un cas où ça avait planté après les 2 calls & il se passait plus rien)

J'ai également ajouté la possibilité de définir une IP que l'on ne souhaite pas supprimer côté o2 (votre IP fixe personnelle par exemple) via un secret (ici c'est secrets.IP_TO_KEEP)

Et le résultat du log que vous devez avoir au moment de l'action :

Get actual whitelisted IPs...
Delete this github IP: *REDACTED* (in & out)
{
  "success": true,
  "message": "L'autorisation a été supprimée avec succès, elle sera effective d'ici quelques minutes (~5mins)."
}
{
  "success": true,
  "message": "L'autorisation a été supprimée avec succès, elle sera effective d'ici quelques minutes (~5mins)."
}
Keep this IP, go to the next...
All non-whitelisted IPs deleted!
Attempt to whitelist IP...
{
  "success": true,
  "message": "L'autorisation a été ajoutée avec succès."
}

@Progi1984
Copy link

On dirait que l'URL https://$LOGIN:$PASSWORD@$SERVER:2083/$ENDPOINT?r=list a encore changé.

@DamChtlv
Copy link

@Progi1984 c'était down il y a quelques jours pour une raison inconnue (le curl ne renvoyait plus rien) mais ça fonctionne à nouveau (testé aujourd'hui)

@Progi1984
Copy link

@DamChtlv Pareil ici. Merci.

@AdrienCpr
Copy link

Bonjour a tous,

J'essaie de faire fonctionner sur un serveur node.js mais je n'arrive pas a passer l'étape de whitelist..
J'ai cette erreur car je récupère un html a la place d'un json :
image

Après avoir essayé sur mon navigateur directement je me suis rendu compte que :

Lorsque j'essaie d'accéder a cet url : https://$LOGIN:$PASSWORD@$HOST:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php?r=list
J'ai une redirection avec ce message ou on me demande de saisir mon mot de passe:
image

Mais lorsque je me connecte dans l'url j'ai un token de session, exemple :
https://$HOST:2083/$TOKEN/frontend/o2switch/index.html

Donc il faudrait que j'ai ça, car la j'ai bien mon JSON :
https://$HOST:2083/$TOKEN/frontend/o2switch/o2switch-ssh-whitelist/index.live.php?r=list

Pouvez vous m'aider svp ? 😿

`name: Build and Deploy Node.js API

on:
push:
branches:
- dev
- main

jobs:
build-deploy:
runs-on: ubuntu-latest

steps:
  # Checkout le code depuis GitHub
  - uses: actions/checkout@v4

  # Installation de Node.js
  - name: Set up Node.js
    uses: actions/setup-node@v3
    with:
      node-version: '22'  # Choisis la version de Node.js que tu utilises

  # Installation des dépendances
  - name: Install dependencies
    run: npm install

  # Récupérer l'IP publique du runner GitHub
  - name: Get runner's public IP
    id: ip
    uses: haythem/[email protected]

  # Ajouter l'IP du runner à la whitelist SSH de o2Switch
  - name: Whitelist IP on hosting & delete github old ones (o2switch)
    shell: bash
    run: |
      ENDPOINT='frontend/o2switch/o2switch-ssh-whitelist/index.live.php'

      echo "Get actual whitelisted IPs..."
      RESPONSE=$(curl -sX GET "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=list")
      echo "Response: $RESPONSE"
      UNIQUE_IPS=$(echo "$RESPONSE" | jq -r '.data.list[] | .address' | sort -u)

      for address in $UNIQUE_IPS; do
        if [[ $address == "${{ secrets.IP_TO_KEEP }}" ]]; then
            echo "Keep this IP, go to the next..."
            continue
        fi
        echo "Delete this github IP: $address (in & out)"
        curl -sX GET "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=in&port=22" | jq
        sleep 3
        curl -sX GET "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=out&port=22" | jq
        sleep 3
      done
      echo "All non-whitelisted IPs deleted!"

      echo "Attempt to whitelist IP..."
      curl -sX POST -d 'whitelist[address]=${{ steps.ip.outputs.ipv4 }}' -d 'whitelist[port]=22' "https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/$ENDPOINT?r=add" | jq

  # Déployer la branche 'dev' sur le préprod
    #      - name: Deploy on develop branch
    #if: ${{ github.ref == 'refs/heads/develop' }}
    #uses: easingthemes/ssh-deploy@main
      #with:
      #SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
      #ARGS: "-rlgoDzvc -i --delete-after"
      #SOURCE: "./"
      #REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
      #REMOTE_USER: ${{ secrets.REMOTE_USER }}
      #TARGET: ${{ secrets.REMOTE_PREPROD_TARGET }}
      #EXCLUDE: "/dist/, /node_modules/"
      #SCRIPT_BEFORE: |
      # whoami
      # ls -al
      #SCRIPT_AFTER: |
      # whoami
      #  ls -al
    #  echo $RSYNC_STDOUT

  # Déployer la branche 'main' sur le prod
  - name: Deploy on main branch
    if: ${{ github.ref == 'refs/heads/main' }}
    uses: easingthemes/ssh-deploy@main
    with:
      SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
      ARGS: "-rlgoDzvc -i --delete-after"
      SOURCE: "./"
      REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
      REMOTE_USER: ${{ secrets.REMOTE_USER }}
      TARGET: ${{ secrets.REMOTE_PROD_TARGET }}
      EXCLUDE: "/dist/, /node_modules/"
      SCRIPT_BEFORE: |
        whoami
        ls -al
      SCRIPT_AFTER: |
        whoami
        ls -al
        echo $RSYNC_STDOUT

`

@madrzejewski
Copy link

@AdrienCpr Est-ce que le mot de passe a bien été encodé au format URLEncode ? Est-ce qu'il contient des caractères spéciaux comme @ ou $ ?

C'est souvent ce qui pose un problème. Si le mot de passe contient des caractères spéciaux, il faut l'encoder avec urlencode sinon les caractères peuvent être interpréter et faire planter la commande.

Si c'est bien bon, ne pas hésiter à contacter le support pour qu'il regarde. Ils sont briefés là-dessus et au pire des cas ils me remontent la demande.

@AdrienCpr
Copy link

Hello @madrzejewski, oui j'ai bien encodé mon mot de passe car il contient des "-" et "(". Mais encodé ou non j'ai la même erreur.

J'ai ouvert un ticket, mais nous n'arrivons pas à trouver ce qui bloque.

Je me suis dis que vous auriez peut être la solution 🥺

@madrzejewski
Copy link

Bon, finalement, c'était le mot de passe qui était invalide à cause d'un caractère ajouté en trop à fin lors du passage en URLEncode.

Il faut faire attention à ce qu'un espace, ou un saut de ligne, ne s'ajoute pas à la fin du mot de passe.
Surtout en passant par le site https://www.urlencoder.org/fr/

Si on note le mot de passe suivi d'un saut de ligne, ce n'est pas bon.
Cela va ajouter un %0A à la fin du mot de passe en version encodé.

@AdrienCpr
Copy link

Oui, merci beaucoup !

Par contre, c'est la première fois que je passe par les github actions et j'ai supprimé tout mes fichier serveur avec :
`# Déployer la branche 'main' sur le prod

  • name: Deploy on main branch
    if: ${{ github.ref == 'refs/heads/main' }}
    uses: easingthemes/ssh-deploy@main
    with:
    SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
    ARGS: "-rlgoDzvc -i --delete-after"
    SOURCE: "./"
    REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
    REMOTE_USER: ${{ secrets.REMOTE_USER }}
    TARGET: ${{ secrets.REMOTE_PROD_TARGET }}
    EXCLUDE: "/dist/, /node_modules/"
    SCRIPT_BEFORE: |
    whoami
    ls -al
    SCRIPT_AFTER: |
    whoami
    ls -al
    echo $RSYNC_STDOUT``

C'est comme si il n'avait pas réussis a lire mon secrets.REMOTE_PROD_TARGET. Je vais essayé de voir ce que je peux faire pour éviter ce problème a nouveau

@Tatar73
Copy link

Tatar73 commented Dec 12, 2024

Bonjour,

Je rencontre un problème, l'étape whitelist IP s'effectue correctement mais lors de l'étape de "Deploy on develop branch", j'ai l'erreur suivante :

[CMD] Remote script failed: Command failed: DEBIAN_FRONTEND=noninteractive ssh -p 22 -i /home/runner/.ssh/deploy_key_1733934840149 -o StrictHostKeyChecking=no @ 'RSYNC_STDOUT="" bash -s' < local_ssh_script-before-9efefef7-57ec-4886-81e3-ba170fc102fa.sh
Load key "/home/runner/.ssh/deploy_key
_1733934840149": error in libcrypto
Permission denied, please try again.
Permission denied, please try again.
@: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).

Pouvez-vous m'aider ? Je ne trouve pas de solution...

@madrzejewski
Copy link

madrzejewski commented Dec 12, 2024

Bonjour @Tatar73

L'erreur semble indiquer un problème avec l'installation de la clé SSH, c'est l'authentification sans mot de passe qui échoue.

La mise en liste blanche fonctionne correctement, ce n'est pas un problème à ce niveau.
Si ce n'était pas le cas, il y aurait un timeout à la place, ça n'arriverait pas jusqu'à l'erreur suivante :

  • @: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).

Là, la connexion se fait bien, c'est à l'authentification que ça bloque.

Il faut vérifier que la clé SSH est bien installé et plus particulièrement les permissions sur les différents fichiers (erreurs courantes) :

  • dossier .ssh : chmod 700
  • fichier .pub (clé publique) : chmod 644
  • fichier .key ou id_rsa : chmod 600
  • authorized_keys : chmod 644

Il faut aussi vérifier qu'il n'y a pas une protection par mot de passe sur la clé privé, généralement ça bloque aussi.

Pour avoir plus d'information, il est possible d'ajouter un '-vv' ou '-vvv' à la commande SSH échoue et la sortie devrait être bien plus verbeuse. Ça peut aider à trouver le problème.

EDIT: Je n'avais pas vu la partie " error in libcrypto", ça semble indiquer que la clé déployée sur le runner est invalide. Il faut vérifier si elle n'est pas tronquée ou altérée à cause d'un copier/coller foireux par exemple.

@d9beuD
Copy link

d9beuD commented Mar 20, 2025

Bonjour à tous,

J'ai un problème similaire à celui de @AdrienCpr, c'est à dire :

Get actual whitelisted IPs...
jq: parse error: Invalid numeric literal at line 2, column 10
Error: Process completed with exit code 5.

Cependant, lorsque j'essaye d'accéder à l'endpoint https://$LOGIN:$PASSWORD@$HOST:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php?r=list, j'ai simplement la page de login que vous connaissez bien s'affichant sans aucune erreur.

Capture d’écran 2025-03-20 à 11 11 43

Sachant que j'utilise la 2FA pour accéder à mon espace cPanel, je me demande si ce ne serait pas lié. Sur d'autres services, il est souvent requis de remplacer le mot de passe par un token lorsque la 2FA est activée.

J'ai donc généré un token dans l'outil Manage API Tokens mais c'est pour utiliser l'API de cPanel, or il me semble que les endpoints qu'on essaye de joindre ici ne font pas partie de l'API cPanel.

Un génie parmi-vous pour m'aider ?


Un extrait de mon workflow avec les modifications proposée par @DamChtlv :

      # Get the public IP of the runner
      - name: Public IP
        id: ip
        uses: haythem/[email protected]

      # Whitelist the public IP of the runner on the remote server by adding it 
      # to the SSH whitelist and wait 65 seconds for the IP to be whitelisted
      - name: Whitelist IP on hosting & delete github old ones (o2switch)
        shell: bash
        run: |
          ENDPOINT='frontend/o2switch/o2switch-ssh-whitelist/index.live.php'

          echo "Get actual whitelisted IPs..."
          UNIQUE_IPS=$(curl -sX GET "https://${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=list" | jq -r '.data.list[] | .address' | sort -u)
          for address in $UNIQUE_IPS; do
            # if [[ $address == "$#{{ secrets.IP_TO_KEEP }}" ]]; then
            #     echo "Keep this IP, go to the next..."
            #     continue
            # fi
            echo "Delete this github IP: $address (in & out)"
            curl -sX GET "https://${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=in&port=22" | jq
            sleep 3
            curl -sX GET "https://${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=out&port=22" | jq
            sleep 3
          done
          echo "All non-whitelisted IPs deleted!"

          echo "Attempt to whitelist IP..."
          curl -sX POST -d 'whitelist[address]=${{ steps.ip.outputs.ipv4 }}' -d 'whitelist[port]=22' "https://${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=add" | jq

@madrzejewski
Copy link

@d9beuD Oui, c'est très probablement la 2FA qui bloque. Il faudrait essayer d'ajuster les appels en appliquant ce que cPanel recommande ici :

2FA with non-session-based authentication

@d9beuD
Copy link

d9beuD commented Mar 20, 2025

✅ Effectivement, c'était bien ça ! Merci pour l'exemple @madrzejewski. Pour y arriver, j'ai dû :

  1. Installer de quoi générer un code OTP sur le runner
- uses: tecolicom/actions-use-apt-tools@v1
  with:
    tools: oathtool gnupg2
  1. Générer un code OTP
OTP=$(oathtool -b --totp '${{ secrets.O2SWITCH_OTP_SECRET }}')
  1. Utiliser une authentification Basic
AUTH_STR="Basic $(echo -n '${{ secrets.SSH_USER }}:${{ secrets.SSH_PASSWORD }}' | base64)"
  1. Utiliser l'authentification sur chaque requête CURL

Il suffit de rajouter -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" et d'enlever ${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@ à chaque requête.

- curl -sX GET "https://${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.SSH_HOST }}:2083/$ENDPOINT?
+ curl -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?"  -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP"

Solution finale

# Get the public IP of the runner
- name: Public IP
  id: ip
  uses: haythem/[email protected]

- uses: tecolicom/actions-use-apt-tools@v1
  with:
    tools: oathtool gnupg2

# Whitelist the public IP of the runner on the remote server by adding it 
# to the SSH whitelist and wait 65 seconds for the IP to be whitelisted
- name: Whitelist IP on hosting & delete github old ones (o2switch)
  shell: bash
  run: |
    ENDPOINT='frontend/o2switch/o2switch-ssh-whitelist/index.live.php'
    OTP=$(oathtool -b --totp '${{ secrets.O2SWITCH_OTP_SECRET }}')
    AUTH_STR="Basic $(echo -n '${{ secrets.SSH_USER }}:${{ secrets.SSH_PASSWORD }}' | base64)"

    echo "Get actual whitelisted IPs..."
    UNIQUE_IPS=$(curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=list" | jq -r '.data.list[] | .address' | sort -u)
    for address in $UNIQUE_IPS; do
      # if [[ $address == "$#{{ secrets.IP_TO_KEEP }}" ]]; then
      #     echo "Keep this IP, go to the next..."
      #     continue
      # fi
      echo "Delete this github IP: $address (in & out)"
      curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=in&port=22" | jq
      sleep 3
      curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=out&port=22" | jq
      sleep 3
    done
    echo "All non-whitelisted IPs deleted!"

    echo "Attempt to whitelist IP..."
    curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX POST -d 'whitelist[address]=${{ steps.ip.outputs.ipv4 }}' -d 'whitelist[port]=22' "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=add" | jq

@d9beuD
Copy link

d9beuD commented Mar 31, 2025

Comme c'est un bout de code assez conséquent et peu pratique à maintenir quand on a plusieurs projets en dépendant, j'ai pris l'initiative de publier une action sur le Marketplace de GitHub Actions.

https://github.com/d9beuD/o2switch-whitelisting

Cela fonctionne très bien avec la 2FA et normalement ça devrait être pareil sans. J'apprécierais vraiment si quelqu'un se dévouait pour tester sans 2FA.

@madrzejewski
Copy link

Un petit message pour dire qu'on est en train de tester une mise à jour de l'outil d'autorisation SSH. Nous avons étendu l'API de cPanel pour y inclure l'outil.

Qu'est-ce que ça change ?

L'outil s'utilise de la même manière que l'API cPanel. Donc il devient compatible avec le système de Token d'API de cPanel. Cela évite de devoir mettre l'identifiant / mot de passe du compte dans les secrets Github.

Ça veut également dire qu'on peut l'utiliser en mode CLI, ça devient un module supplémentaire de la UAPI cPanel.

À cet instant, cela n'est pas publié sur tous les serveurs. C'est en phase de test. Je l'ai utilisé dans le workflow de notre nouvelle documentation et c'est fonctionnel.

Il reste 2-3 choses à revoir, notamment dans le format des réponses, mais ça devrait être disponible partout prochainement.

On va sans doute garder l'ancienne méthode en complément, pour ne pas casser des workflows déjà utilisés.

@d9beuD
Copy link

d9beuD commented Apr 21, 2025

Merci pour l'information, je mettrai à jour mon action pour que ce soit prêt une fois la fonctionnalité disponible sur tous les serveurs. Avez-vous un temps estimé à communiquer @madrzejewski ?

@madrzejewski
Copy link

Je pense que ça sera déployé d'ici une ou deux semaines. L'outil est prêt, il reste juste 2-3 petits détails à corriger, c'est pas grand chose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment