Git hooksによるアクセス制限実現方法

共有フォルダにgitリポジトリを置き、それを共有リポジトリにすることで、簡易的にソースコードをgit運用できる。この時、簡易的なリポジトリへのアクセス制限をかけたい時がある。
この記事では、ユーザー単位、リポジトリ単位でのリポジトリの更新制限をかけるためのgit hookを紹介する。
あくまでもうっかりミスを防ぐ目的であり、厳密なセキュリティ確保には使えないので注意してほしい。


# ファイルサーバーでの共有リポジトリの作り方のおさらい

共有リポジトリの作り方は、git initコマンドで --bare --shared オプションを付ければよい。
--bareオプションは作業ディレクトリを作らないオプション、--sharedはパーミッションを共有可能にするオプションである。

```console:server01
mkdir myproj1.git
cd myproj1.git
git init --bare --shared
```

上記の方法で初期化したリポジトリをファイルサーバ上で共有し、ローカルでクローンすれば、その共有フォルダをリモートリポジトリとするローカルリポジトリを作ることができる。

```console
git clone \\server01\myproj1.git
```

# git hooksでアクセス制御

上記の方法で作ったリポジトリにはユーザー制御機能もないし、もちろんGitHubのようなプルリクエスト機能もない。それでも、gitの基本的な機能は全て利用できるし、社内のみで開発したい場合などには非常に便利である。

とはいえ、複数人で開発していると、「このブランチには特定の人だけがpushできるようにしたい」という要望が出てくる。厳密にアクセス制御したいというよりは、間違って自分と関係のないブランチを更新してしまうミスを防ぐためだ。

これはgitの標準機能であるhooksという機能を用いると実現できる。
hooksは、gitが何らかの操作を行う際に、そこに割り込んでチェックをかけることができる。

詳しい説明はこちらの記事が詳しい。

https://qiita.com/Syuparn/items/9e107e0aaf1a61b260b5

このhookを使って、「このブランチには特定の人だけがpushできるようにしたい」という要望を実現する方法を検索したところ、以下の記事がヒットした。

https://maku77.github.io/git/limit-push-users.html

ただ、当方の環境(Windows Server 2019、クライアントPCはWindows 10)では$USERが空だった為、git configから取得するように変更した。
また、メールのドメインのみの指定でも許可できるようにした。

尚、gitユーザーのメールアドレスはユーザーが自分で設定できるものであり、セキュリティを確保する目的で使うと簡単にかいくぐられてしまうので注意されたい。


```hooks/authorized_users
[refs/heads/develop]
@yourdomain111.jp
@yourdomain222.jp

[refs/heads/main]
@yourdomain111.jp
@yourdomain222.jp

[refs/heads/feature]
@yourdomain111.jp
@yourdomain222.jp
```


```bash:hooks/update
#!/bin/sh
#
# Git's update hook for submit control
#

# User info
gitusername=$(git config user.name)
gituseremail=$(git config user.email)

# User setting file
readonly AUTH_USERS_FILE='authorized_users'

# Branch name the user is about to push to.
target_branch="$1"

# Fullpath of hooks dir.
self_dir=$(cd $(dirname $0); pwd)

# 1 if the target branch name can be found in the $AUTH_USERS_FILE.
is_restricted=0

# Parenthetic section which represents a branch name in the $AUTH_USERS_FILE.
current_section=''

# Search user names.
while read line
do
    # Remove comment.
    line=${line%%#*}

    # Remove trailing spaces.
    line=$(echo $line | sed -e 's/ *$//')

    # Empty line.
    if [ -z "$line" ]; then
        continue
    fi

    # Parse section name. e.g. 'refs/heads/master'.
    section=$(echo $line | sed -n -e 's/\[\(.\+\)\]/\1/p')
    if [ -n "$section" ]; then
        if [ "$section" = "$target_branch" ]; then
            is_restricted=1
        fi
        current_section="$section"
        continue
    fi

    # Check if the user is listed.
    if [ "$current_section" = "$target_branch" ]; then
        # Check entire email address
        if [ "$gituseremail" == "$line" ]; then
            # The user can push to the branch.
            exit 0
        fi
        
        # Check email domain
        if [ ${line:0:1} == "@" ]; then
        	if [[ "$gituseremail" =~ "$line" ]]; then
        		# The user can push to the branch.
        		exit 0
        	fi
        fi
        
    fi
done < "$self_dir/$AUTH_USERS_FILE"

if [ $is_restricted = 0 ]; then
    # No restriction. The user can push to the branch.
    exit 0
fi

echo ""
echo "--------------------------------------------------------------"
echo "[$target_branch] は hooks/update でアクセス制御されています."
echo "あなた($gituseremail)はこのブランチの更新許可がありません."
echo "このブランチの更新が必要な場合、管理者にご連絡ください."
echo "--------------------------------------------------------------"
echo ""

# The user cannot push to the branch.
exit 1
```