Journal Serveur Git perso via SSH

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
29
6
mai
2024

Petit journal pour faire un serveur Git perso, SSH uniquement, pas d'accès web. Pas de fioritures, mais le minimum doit fonctionner :

  • Création de dépôt automatique.
  • Restriction d'accès, seul un accès avec Git autorisé.

Sans doute plus utile pour un backup de projets personnels que pour une collaboration à grande échelle, mais ce n'était pas le but.

Je passe la configuration du serveur SSH, qui doit est spécifique à chaque utilisateur, mais on peut également limiter l'accès au niveau du serveur SSH :

Match Group users
    AllowTcpForwarding no
    AllowStreamLocalForwarding no
    AllowAgentForwarding no
    PermitTunnel no
    PermitTTY no
    PermitUserRC no
    ForceCommand /usr/local/bin/git-only

Script d'encapsulation Git

#!/bin/sh -e
#
## ---
## This script allow git access to user repositories.
## Git repositories are automatically created on first push.
## Valid repository names should start with a letter, followed
## by alphanumeric or hyphen, underscore and dot characters
## Repositories are stored in /home by default.
## Example of repository creation:
## $ git remote add personal user@domain:repo-name.git
## $ git push --all --tags personal
## ---
#

# Error codes
SUCCESS=0
NO_CMD=10
NO_PATH=20
NO_ROOT=30
INVALID_NAME=40
SYS_ERROR=50

usage() {
    sed -n 's/^## //p' "$0" 1>&2
}

if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    usage
    exit
fi

if [ -n "$SSH_CONNECTION" ] && [ -z "$SSH_ORIGINAL_COMMAND" ]; then
    echo "Your key can only be used for git" 1>&2
    usage
    exit $NO_CMD
fi

# Get the user running the script
calling_user=$(whoami)

# This shouldn't be run by root
if [ "$calling_user" = "root" ]; then
    echo "This script should not be run by root" 1>&2
    usage
    exit $NO_ROOT
fi

# Log the command run
logger "Running $SSH_ORIGINAL_COMMAND for user '$calling_user' (from $SSH_CLIENT)"

# Do not create world readable files and directories
umask 077

# Set the git root directory
git_root_dir="/home/$calling_user/git/"
git_repo_dir="$git_root_dir/repositories"

if [ ! -d "$git_repo_dir" ]; then
    mkdir -p "$git_repo_dir"
fi

# Everything should run in this directory
cd "$git_repo_dir" || exit $SYS_ERROR

# This block automatically creates the git directory the first time we push
is_push=$(echo "$SSH_ORIGINAL_COMMAND" | grep -E -c '^git-receive-pack.*')

if [ "$is_push" = "1" ]; then

    # Check if the folder exists
    repo_path=$(echo "$SSH_ORIGINAL_COMMAND" | head -n 1 | cut -f 2 -d ' ' | tr -d "'")

    # Make sure we don't pass the home directory
    if expr match "$repo_path" ".*~.*" >/dev/null; then
        echo "Do not specify the user path for your git repository" 1>&2
        usage
        exit $NO_PATH
    fi

    if expr match "$repo_path" ".*/.*" >/dev/null; then
        echo "Do not specify any absolute path for your git repository" 1>&2
        usage
        exit $NO_PATH
    fi

    # Cleanup the repository name
    repo_name=$(echo "$repo_path" | sed -E "s:[^a-zA-Z]*([a-zA-Z][a-zA-Z0-9_\.-]+).*:\1:")

    # Make sure the repository name was clean
    if [ "$repo_path" != "$repo_name" ]; then
        echo "The name $repo_path is not valid, use '$repo_name' instead." 1>&2
        echo "Valid repository name should start by a letter, then alphanumeric or (-) (_) and (.)"  1>&2
        usage
        exit $INVALID_NAME
    fi

    if [ ! -d "$repo_name" ]; then
        logger "Creating new repository '$repo_name'"
        git init --bare "$repo_name" >/dev/null 2>&1
    fi

fi

exec git-shell -c "$SSH_ORIGINAL_COMMAND"

Exemple de configuration des clés autorisées

Dans le fichier ~/.ssh/authorized_keys:

command="/usr/local/bin/git-only" ssh-ed25519 AAAAC3NzaCKlZDIOpOTE5AAAAIBY4lPxia6FXSSI/KCu453tWdX8ERWwyl/CHfPVxDYu git-only-2024
  • # gitblit ?

    Posté par  . Évalué à 4 (+4/-1).

    https://github.com/gitblit-org/gitblit
    https://www.gitblit.com/
    petit serveur git sans fioriture qui ne veut pas se prendre pour jenkins

    Bon, la dernière release a quand même 2 ans

    • [^] # Re: gitblit ?

      Posté par  (site web personnel) . Évalué à 8 (+7/-1).

      Fut un temps j'utilisais gitolite3 pour ça.

      Adhérer à l'April, ça vous tente ?

      • [^] # Re: gitblit ?

        Posté par  (site web personnel) . Évalué à 5 (+3/-0).

        J'avoue que j'ai été tenté, vu que c'est dans Debian et bénéficie des mises à jour de sécurité.

        Cependant, pour mon usage personnel, et sauvegarder des "dotfiles" et des projets persos, Git+SSH fait exactement ce que je voulais.

        Si je devais aujourd'hui gérer un serveur Git pour une équipe de développeurs, je m'orienterais plutôt vers Gitea ou GitLab.

  • # Config que j'ai mise en place

    Posté par  . Évalué à 9 (+8/-0).

    Merci d'ouvrir le sujet, c'est l'occasion de partager !

    De mon côté, j'ai utilisé git-shell qui est fourni pour limiter les commandes autorisées aux utilisateurs git (chsh $USER -s /usr/bin/git-shell)

    Pour freiner un peu les attaques force brute sur le port ssh, n'autoriser que l'accès par clé ? J'ai aussi installé fail2ban, même si j'ai vu passer la publication d'un système plus efficace, ça c'est standard et livré dans la distrib.

    Enfin, pour faire un miroir de repos pleins de binaires qui utilisent LFS, j'ai installé rsync, ajouté un ln -s /usr/bin/rsync ~$USER/git-shell-commands/. Côté utilisateur il faut alors utiliser git-lfs-rsync-agent. C'est pas une foudre de guerre mais ça fait le job !

    • [^] # Re: Config que j'ai mise en place

      Posté par  (site web personnel) . Évalué à 10 (+9/-0).

      Merci, je regarderai git-shell, sûrement dans Debian aussi.

      Pour les attaques SSH, j'utilise les sets dynamiques de nftables, très léger, ne nécessite pas de service en tâche de fond, par exemple :

      table inet filter {
      
          set banned_ipv4 {
              type ipv4_addr . inet_service
              flags dynamic,timeout
              timeout 4h
          }
      
          set banned_ipv6 {
              type ipv6_addr . inet_service
              flags dynamic,timeout
              timeout 4h
          }
      
          chain input {
      
              # Limit new SSH connections ala fail2ban
              meta nfproto ipv4 tcp dport ssh ct state new,untracked \
              limit rate over 10/minute add @banned_ipv4 { ip saddr . 22 }
      
              meta nfproto ipv6 tcp dport ssh ct state new,untracked \
              limit rate over 10/minute add @banned_ipv6 { ip6 saddr . 22 }
      
              # Allow ssh
              tcp dport ssh ct state new counter accept \
              comment "New ssh connections"
      
          }
      
      }
      
      
  • # Gittea

    Posté par  (site web personnel) . Évalué à 6 (+5/-0).

    Perso j'utilise Gittea

    C'est un mini Github/Gitlab like. C'est léger, écrit en Go et ca tourne sur tous les OS.
    Il faut pas grand chose pour le faire tourner et ca donne tout ce qu'il faut dont des issues, wiki et meme de la CI.

    Bref, c'est bien plus simple à gérer qu'un truc à la main je trouve.

    • [^] # Re: Gittea

      Posté par  (site web personnel) . Évalué à 3 (+1/-0).

      Question :

      Je suis suis Debian, et je suis intéressé également par Gitea.

      Comment sont installées les mises à jour de sécurité ?

      Je n'ai pas trouvé de dépôt Debian.

      Merci.

      • [^] # Re: Gittea

        Posté par  . Évalué à 3 (+2/-0).

        J'ai fait un script pour l'automatisation des mises à jour.

        #!/bin/bash
        # Téléchargement et installation du binaire gitea depuis Github
        
        # set -euo pipefail
        
        CURL=$(which curl)
        CHMOD=$(which chmod)
        CUT=$(which cut)
        ECHO=$(which echo)
        GITEA=$(which gitea)
        GREP=$(which grep)
        MV=$(which mv)
        SED=$(which sed)
        RM=$(which rm)
        
        SYSTEMCTL=$(which systemctl)
        
        GITEA_BINARY=/usr/local/bin/gitea
        
        # Définit le dossier du script comme dossier courant
        cd "${0%/*}" || exit
        
        ${ECHO} "Mise à jour de gitea ..."
        
        VERSION=$(${CURL} --silent https://api.github.com/repos/go-gitea/gitea/releases/latest | ${GREP} '"tag_name":' | ${CUT} -d'"' -f4 | ${SED} 's/v//')
        
        VERSION_LOCAL=$(${GITEA} --version 2>/dev/null | ${CUT} -d " " -f 3)
        ${ECHO} "Version distante : ${VERSION}"
        ${ECHO} "Version locale : "$VERSION_LOCAL
        
        if [[ ${VERSION} == ${VERSION_LOCAL} ]]; then
          ${ECHO} "Déjà à jours, fin des opérations"
          exit
        fi
        
        # Stop Gitea
        ${SYSTEMCTL} stop gitea
        
        # Sauvegarde de l'ancien binaire
        if test -f "${GITEA_BINARY}"; then
          ${ECHO} "Sauvegarde du binaire"
          ${MV} "${GITEA_BINARY}" "${GITEA_BINARY}".bak
        fi
        
        TELECHARGEMENT=$(${CURL} -s -w "%{http_code}" -S -L https://github.com/go-gitea/gitea/releases/download/v${VERSION}/gitea-${VERSION}-linux-amd64 -o ${GITEA_BINARY}) > /dev/null 2>&1;
        if [[ ${TELECHARGEMENT} -eq 200 ]]; then
          ${ECHO} "Nouvelle version téléchargé, remplacement du binaire..."
        
          # ${MV} /tmp/gitea "${GITEA_BINARY}"
          ${CHMOD} +x "${GITEA_BINARY}"
        
          # Redémarrage du service gitea
          ${SYSTEMCTL} start gitea
          VERSION_LOCAL=$(${GITEA_BINARY} --version | ${CUT} -d " " -f 3)
        
          if [[ ${VERSION} == ${VERSION_LOCAL} ]]; then
            ${ECHO} "Mise à jour réussit, suppression de la sauvegarde"
        
            # Suppression de l'ancien binaire
            if test -f "${GITEA_BINARY}.bak"; then
              ${RM} "${GITEA_BINARY}.bak"
            fi
            exit
          fi
        else
          ${ECHO} "Erreur lors du téléchargement, restauration de la sauvegarde"
          if test -f "${GITEA_BINARY}.bak"; then
            ${MV} "${GITEA_BINARY}.bak" "${GITEA_BINARY}"
          fi
        fi
        exit
        
        ${ECHO} "Un problème est survenu."
        exit 1
        
        
        
        • [^] # Re: Gittea

          Posté par  . Évalué à 1 (+0/-1).

          c'est moi, ou ça casse juste l'argument de Raoul Hecky au sujet de pas utiliser de script à la mimine? :)

          • [^] # Re: Gittea

            Posté par  . Évalué à 3 (+2/-0).

            Non, c'est pour les mises à jour de l'outil, lui, il parle de l'outil.
            Si ce n'est pas dans les dépôts, il faut bien faire les mises à jour.

            • [^] # Re: Gittea

              Posté par  . Évalué à 3 (+1/-0).

              <troll>sinon vous pouvez installer la version snap</troll>

        • [^] # Re: Gittea

          Posté par  . Évalué à 5 (+3/-0).

          Pourquoi passer par des variables pour tout y compris pour des trucs qui existent en built-in ?

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

          • [^] # Re: Gittea

            Posté par  (site web personnel) . Évalué à 3 (+1/-0).

            Pourquoi passer par des variables pour tout y compris pour des trucs qui existent en built-in ?

            Je plussoie, je n'ai rien or dire, mais bon.

            Je crois que c'est une vielle pratique du temps où Unix utilisait des chemins différents pour chaque distribution.

            Un truc de vieux barbus, AMHA.

          • [^] # Re: Gittea

            Posté par  . Évalué à 2 (+1/-0).

            Pour plusieurs raisons :
            - Toutes les distributions n'ont pas les binaires aux mêmes endroits
            - J'ai eu trop de problèmes avec la crontab, depuis que je fais ça, plus de problèmes

            Les trucs qui existent en "built-in", which ne renvoi rien.

            • [^] # Re: Gittea

              Posté par  . Évalué à 4 (+2/-0).

              Which va chercher les commandes dans le path, c'est exactement ce que fait bash. Si bash ne trouve pas la commande, which n'y arrivera pas plus (c'est le principe même de faire le même algorithme que bash). Par contre effectivement les commandes peuvent être à des endroits divers et dans les cas les plus exotiques, ta méthode va planter. Il suffit qu'il y ai par exemple une espace dans le chemin du binaire.

              Quand which be te renvoi rien le reste du script va planter, mais c'est plus qu'en utilisant bash on s'attend à utiliser le built-in plutôt que le binaire.

              J'ai eu trop de problèmes avec la crontab, depuis que je fais ça, plus de problèmes

              C'est de l'incantation car ton PATH était mal configuré. Tu te retrouvais probablement dans un bourn shell ou tu n' héritait pas des bonnes variables d'environnement. Tinter autant ton script avec une pratique exotique pour un problème aussi limité (d'ailleurs sous linux c'est l'un des gros avantages à utiliser les timers systemd) me semble très dommage et sujet à erreur (là par exemple tu as 2 variables qui pointent sur le binaire de gitea une qui passe par which et une en dure).

              https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

              • [^] # Re: Gittea

                Posté par  . Évalué à 1 (+0/-0).

                Pour la version du binaire en dur, c'est celui que je télécharge, je suis à peu près sûr de l'endroit où il va être. Je vais voir l'amélioration possible.

                Je conviens que si j'avais mieux maitrisé la crontab, j'aurais pu éviter de faire ça.

                C'est un script perso que je n'avais pas prévu de partager.

                Je n'aime pas systemd, j'ai toujours du mal à y penser. Pour information, dans les timers, tu n'as pas de problème avec le path ?

                Comme je dois être régulièrement compatible avec FreeBSD, je n'arrive pas à trouver un système de fichiers qui utilisent les ACL NFS v4 pour Linux, je pense passer à python pour faire les scripts.

                • [^] # Re: Gittea

                  Posté par  . Évalué à 3 (+1/-0).

                  Je n'aime pas systemd, j'ai toujours du mal à y penser. Pour information, dans les timers, tu n'as pas de problème avec le path ?

                  L'environnement d'execution d'un timer est bien mieux maitrisé. Avec cron c'est embêtant par exemple de lancer une cron avec un utilisateur particulier parce que ça veut dire qu'il faudra se connecter sur cet utilisateur pour le lister et en avoir des rapports (via les mails). Avec les timers c'est une option et tu vois le timer dans la même liste que les autres (avec quand est-ce qu'il s'est lancé la dernière fois et quand est-ce que ce sera la prochaine fois). Tu n'a pas besoin de te souvenir de l'utilisateur en question aussi.

                  Tu peux définir la fréquence d'exécution de manière un peu plus simple et bien plus lisible et tu peut le rendre dépendant de la timezone (si tu veux que quelque chose soit lancé à 8h que ce soit le moment de l'année).

                  Tu as un système de templating qui n'est pas intuitif mais qui marche bien. Ca permet de lancer des tâches avec des options différentes (tu peut lancer un backup quotidien/hebdomadaire incrémentale/complet avec un même script).

                  https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

                  • [^] # Re: Gittea

                    Posté par  . Évalué à 1 (+0/-0).

                    Si tu mets ta tâche dans /etc/cron.d, tu peux préciser un utilisateur.

                    Va falloir que je vérifie plus en profondeur pour le fuseau horaire dans crontab.

        • [^] # Re: Gittea

          Posté par  . Évalué à 6 (+4/-0).

          Si tu veux améliorer un poil :

          au lieu de :

          • stop du service
          • télécharger la nouvelle version
          • rotation du binaire
          • start du service

          faire :

          • télécharger la nouvelle version
          • et la signature
          • et la somme de contrôle
          • et la signature de la somme de contrôle
          • et la signature de la signature de la somme de contrôle (etc… :D)
          • vérifier les papiers d'identité de tout ce beau monde
          • si ok:
            • stop du service
            • rotation du binaire
            • start du service

          Tous ces trucs qu'un gestionnaire de paquet fait gratos ;).

          • [^] # Re: Gittea

            Posté par  . Évalué à 2 (+1/-0).

            Je fais des paquets, mais pas encore de dépôt. Comme je n'ai pas trouvé de dépôt, il faut bien faire les mises à jour.

            Merci pour les conseils, je n'avais pas fait attention à l'ordre des actions et j'ai oublié de vérifier la somme de contrôle.

    • [^] # Forgejo :-)

      Posté par  . Évalué à 7 (+5/-0). Dernière modification le 07 mai 2024 à 15:01.

      Et du coup il va de soi de mentionner son fork maintenu par Codeberg, Forgejo.

      C'est peut-être encore un drop in replacement (genre, il suffit de remplacer le binaire et voilà), en tout cas ça l'était l'année dernière quand j'ai migré de l'un à l'autre.

      Et je confirme que c'est agréable à mettre en place et à utiliser.

      On ne peut plus simple… si on a déjà un reverse proxy bien configuré évidemment. Sinon, une solution plus simple et plus appropriée quand une interface web n'est pas souhaitée.

      Il y a un dépôt Debian mais il est à la traine je crois. Du coup pour les mises à jour, je suis le flux RSS du site principal.

  • # Avec gitolite

    Posté par  (site web personnel) . Évalué à 3 (+1/-0). Dernière modification le 10 mai 2024 à 13:25.

    https://gitolite.com/gitolite/overview.html

    «Gitolite sits on top of "standard" git and openssh, which are assumed to already be installed.»

    Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

  • # Avec Docker

    Posté par  . Évalué à 1 (+0/-0).

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.