Pages

Saturday, 26 March 2016

在debian vps上搭建 git 仓库服务器gitolite

安装 gitolite

git 服务器软件一般常用的曾经是 gitosis, 不过 gitosis 从 2009年就没人维护了, 目前推荐的是使用 gitolite.
再 Ubuntu 上安装 gitolite 非常简单, 只需要再命令行输入
apt-get install gitolite
就安装完成了. 比起之前的 gitosis 简单了不少.

初始化 gitolite

设置 git 用户

管理 git 仓库, 和 apache 之类的软件一样, 也需要一个专门用户, 一般推荐使用 git 我们这里为系统添加一个叫 git 的用户, 输入
adduser --system \
 --shell /bin/bash \
 --group \
 --disabled-password \
 --gecos 'git version control' \
 --home /var/git \
 git
这样就添加了一个叫 git 的用户, 他的主目录是 /var/git 也就是日后我们存放 git项目的地方.

初始化 git 仓库

首先需要上传一个管理员的 ssh 证书, 这里我把我当前用户的公钥匙传到服务器上(如过不知道怎么生成 ssh 证书请 google)
scp ~/.ssh/id_rsa.pub root@server:/tmp/admin.pub
这里的 server 是服务器地址, admin.pub 可以自行改名, 比如叫 sunteya.pub, 作用是再之后初始化 git 库的使用 默认的管理员用户, 如果叫 sunteya.pub 则管理员用户是 sunteya, 以此类推.
上传完证书以后, 我们切换到 git 用户, 准备初始化 gitolite
su - git
接着输入
gl-setup /tmp/admin.pub
这里的 admin.pub 也根据你实际的上传的文件来输入, 接着按照提示就完成了 git仓库的搭建了

管理和添加项目

再服务器搭建好 gitolite 就可以通过他的 gitolite-admin 项目来管理 git仓库了. 我们可以输入
git clone git@server:gitolite-admin.git
取出管理项目, 这时候我们可以看到 gitolite-admin 目录下有
conf/gitolite.conf
keydir/admin.pub
其中 keydir 用于管理用户, 每一个 keydir 下的文件就是一个用户. 另外我们查看 conf/gitolite.conf
repo    gitolite-admin
        RW+     =   admin

repo    testing
        RW+     =   @all
可以看到默认帮我们建了一个叫 testing 的库, 我们可以编辑 conf/gitolite.conf 来添加或者删除 git仓库, 具体的可以访问 http://sitaramc.github.com/gitolite/doc/gitolite.conf.html 查看详细的配置写法, 然后 push 到服务器, 就可以添加或者管理git仓库了.

question:为什么我,su - git 切换的时候还是被要求输入密码,关键是我没设密码.
answer:那是因为 你当前用户不是 root.
是 root 就不需要密码了
另外你可以 sudo su - 切换到 root 用户.
------------------------

搭建git服务器-gitolite

 

对于几个人的小团队,可以自行在公司内网搭建git服务器,实现版本控制。配合gitolite实现权限控制。
我搭建的服务器框架大致如下图:

那么以下的搭建操作就是基于这个图进行配置。每个节点都是ubuntu 16.04发行版,图示有4个节点
节点 功能
Server 中心化的git仓库,本文假设IP为192.168.100.100
Alice 网管,负责创建仓库或者各种访问权限
Bob 项目组长,负责Code-Review和Merge分支到master,拥有修改仓库权限
Carl 项目开发者,只能在自己分支上面修改

搭建服务器

搭建服务器操作在Server和Alice节点进行

Alice端

生成一个rsa密钥对
cd /tmp
ssh-keygen -t rsa -b 4096 -C "alice"
假设生成的公私钥为
# 私钥
~/.ssh/alice
# 公钥
~/.ssh/alice.pub
将私钥写入当前用户ssh配置文件中
vi ~/.ssh/config
# 添加
Host server
  User git
  Hostname 192.168.100.100
  Port 22
  ServerAliveInterval 30
  IdentityFile ~/.ssh/alice
将公钥上传到Server备用
scp ~/.ssh/alice.pub root@server:/tmp/

Server端

留意gitolite的README,提到依赖的软件,有最低版本的要求
  • git 1.6.6 or later
  • perl 5.8.8 or later
  • openssh 5.0 or later
创建git账户并切换到新帐户
adduser git
su git
cd ~
创建空的ssh配置目录
mkdir -p ~/.ssh
克隆gitolite仓库
git clone https://github.com/sitaramc/gitolite
cd gitolite
创建一个目录存放gitolite二进制文件,然后安装
mkdir -p ~/bin
./install -to ~/bin
设置Alice的公钥,这样Alice就成为了gitolite管理员
~/bin/gitolite setup -pk /tmp/alice.pub
执行上面一条命令后,/tmp/alice.pub 被拷贝到~/.gitolite/keydir目录下,并且仓库gitolite-admin克隆到本地后,keydir目录也有一份alice.pub。
所有仓库存放在~/repositories下,gitolite会自动修改~/.ssh/authorized_keys实现不同用户的访问。
因此单独使用一个git用户的目的是,不希望用户手动修改authorized_keys里面的内容,而是通过gitolite来间接修改它。

仓库创建与权限

修改访问权限在Alice节点进行
克隆admin仓库,因为服务器只有Alice的公钥,其它用户无权访问。
cd ~
git clone git@server:gitolite-admin
cd gitolite-admin
直接编辑这个conf文件实现权限管理
conf/gitolite.conf
详细的权限和仓库创建可以参考官方README:
http://gitolite.com/gitolite/conf
https://github.com/sitaramc/gitolite#adding-users-and-repos
比如我修改为
repo foo
    RW+                     =   bob
    -   master              =   carl
    -   refs/tags/v[0-9]    =   carl
    RW+ carl                =   carl
    R                       =   carl
那么达到的效果是:
  1. 创建了一个名字为foo的仓库
  2. RW+表示可读可写可overwrite,Bob拥有仓库最大权限
  3. 减号说明Carl没有master分支和tags的读写权限
  4. Carl只能在自己分支(carl分支)上面进行修改,拥有carl分支的最大权限
  5. Carl可以读取其它分支,这时候就可以读master分支了
注意等号后面的名字是跟ssh公钥文件名字对应的,如果gitolite-admin/keydir目录下的公钥文件名字是
carl_ssh_key.pub
那么等号后面的内容就不是carl,而是carl_ssh_key
为了与conf/gitolite.conf中的帐户对应,创建ssh公私鈅要保存为正确的文件名。
# 输出id_rsa的时候,保存为~/my_gitolite_keys/bob
ssh-keygen -t rsa -b 4096 -C "bob"

# 输出id_rsa的时候,保存为~/my_gitolite_keys/carl
ssh-keygen -t rsa -b 4096 -C "carl"
把~/my_gitolite_keys/中对应的公私鈅交给Bob和Carl,下面测试步骤,要用到公私鈅
将公钥文件添加到gitolite-admin仓库中
cp ~/my_gitolite_keys/*.pub ~/gitolite-admin/keydir/
修改conf和生成密钥完毕,就可以commit,然后将新配置push给server端
cd ~/gitolite-admin
git add *
git commit -m "add user Bob, Carl; generate keys"
git push
那么server端在push结束后自动执行perl脚本,实现权限管理。

测试

测试git在Bob和Carl节点进行
将由Alice交给Bob和Carl的公私鈅,分别存放到各自节点的.ssh目录下
节点 公私鈅存放路径
Bob 公钥 ~/.ssh/bob.pub
私钥 ~/.ssh/bob
Carl 公钥 ~/.ssh/carl.pub
私钥 ~/.ssh/carl

Bob节点

将私钥写入当前用户ssh配置文件中
vi ~/.ssh/config
# 添加
Host server
  User git
  Hostname 192.168.100.100
  Port 22
  ServerAliveInterval 30
  IdentityFile ~/.ssh/bob
本地克隆服务器上的foo仓库,测试修改代码
cd ~
git clone git@server:foo.git
cd foo
echo "hello world" > README.md
git add README.md
git commit -m "print hello world"
git push
这样即验证了Bob有读写服务器master分支的权限

Carl节点

将私钥写入当前用户ssh配置文件中
vi ~/.ssh/config
# 添加
Host server
  User git
  Hostname 192.168.100.100
  Port 22
  ServerAliveInterval 30
  IdentityFile ~/.ssh/carl
本地克隆服务器上的foo仓库,测试修改代码
cd ~
git clone git@server:foo.git
cd foo
echo "try to modify branch master" > README.md
git add README.md
git commit -m "invalid commit"
git push
这么做push的话会被拒绝,即验证了Carl没有写服务器master分支的权限。
下面测试在carl分支的工作流程
情况1: 如果上游没有carl分支,可以添加一个carl分支并Push到服务器上
git checkout -b carl
echo "create branch carl" > README.md
git add README.md
git commit -m "branch: carl created"
git push --set-upstream origin carl
情况2: 如果上游已经存在carl分支,直接切换到carl分支
git checkout carl
经常性使用git pull以拉取服务器上最新的代码版本。

安全设置

修改默认的shell为gitolite专用的,而不是默认的bash
sudo vi /etc/passwd
# 修改git用户的shell程序为/home/git/bin/gitolite-shell
不允许git用户使用密码登陆
sudo vi /etc/ssh/sshd_config
# 增加以下
Match User git
    PasswordAuthentication no

# 重启ssh服务
sudo service ssh restart

参考链接

ssh key配置
gitolite-README
gitolite搭建git仓库管理服务
----------------------------------------------

Enabling git access via password authentication with gitolite


We recently started using gitolite at my workplace. Our previous git hosting setup involved manually managing linux users and groups on the Ubuntu server, which was needlessly time consuming and painful. There were times when file permissions got out of sync because the repo was deployed by user X from his workstation (where group permissions were not setup correctly for a shared git repository), and other headaches. <–more–> We needed something easier. We did not want to go to a workflow where there is one git user on the system, every employee authenticates with their own public key to that user, and everyone with a key has access to everything. We wanted more granular control, but also an easier management workflow. While gitolite does use a single system user under the covers, it provides per-user access control (governed by public keys), easy repo management, and a couple other nice features like wildcard repositories and branch-level access restrictions.

However, one of our developers is in the habit of accessing a random server, cloning a repo (using his password), and pushing from environments other than his development environment. Obviously, his private key is not going to be on each of the machines he does this from. Gitolite does not allow password-based access for obvious reasons — it needs to know who you are to determine permissions — so gitolite was cramping his style.

There are a couple ways around this:

  1. Add a public key for each system he pushes changes from. Since he often logs in as “administrator” or “root” on individual systems, this obviously is not a good solution.

  2. Have the developer enable SSH Agent Forwarding from his machine. This works well, as long as the user does not chain sessions (connect to server A, from there to server B, and clone on B) or is willing / able / allowed to enable Agent Forwarding on each system in the chain. (NOTE: on Mac OS X Snow Leopard, enabling SSH Agent Forwarding is as simple as running ssh-add and adding two lines to your ~/.ssh/config file:

    # add key to agent (I do this in ~/.profile)
    ssh-add $HOME/.ssh/id_rsa
    
    # ~/.ssh/config
    Host *
        ForwardAgent yes
    
  3. Come up with something else.

What we really wanted was a way to allow password-based access to the repositories. Since gitolite does not support this itself, we had to come up with another way. As it turns out, doing so is not too bad, though it does involve a little bash shell black magic.

What we came up with is creating a system user on the git server for each developer who wants password access to git repositories, using a custom script as their defined shell (instead of /bin/sh or /bin/bash). Don’t forget to make your new script executable.

This script needs to look at what the user is trying to do (git action, gitolite ADC, some random shell command, nothing…) and act accordingly. Here’s the script we came up with, which will be dissected below to explain what each part does in more detail than the inline doc (gist).

#!/bin/bash
shift # get rid of -c

# if no commands, just open a shell
if [[ $# -eq 0 ]]; then
        /bin/bash -l

# if the first arg is a git- command, that means it is something like git-push, etc... so forward it
elif [[ $1 == git-* ]]; then
        ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no git@localhost $*

# if the first arg is SET_ENV_ONLY, we sourced this file in order to set up the gitolite function
elif [[ $1 == "SET_ENV_ONLY" ]]; then
        gitolite () {
                ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no git@localhost $*
        }

# if there is at least one non-git command, source this file in a new shell and create the 'gitolite' function
else
        /bin/bash -c "source $0 shiftme SET_ENV_ONLY; $*"
fi

If the user is not trying to do anything (just wants to open a shell), then there are no arguments and we should just open a bash prompt:

if [[ $# -eq 0 ]]; then
        /bin/bash -l

Okay, the simple bit is out of the way. Now on to the black magic.

If the user is trying to perform a git command (via git push, etc.) then we create a new SSH session to localhost (remember, this is running on the same server as gitolite) and pass along the commands ($*). Since the user has already logged in, this ssh command will run from their account using their private / public key. The git commands are things like git-receive-pack, git-upload-pack, etc, so we match the first script argument against “git-“.

Finally, we don’t care about validating the host’s fingerprint (we are already on the host) and there can be no man in the middle (since we are going to localhost), so we can turn off StrictHostKeyChecking. We also don’t want to print out the various fingerprint messages that SSH will generate when you ignore host keys, so we make the new SSH connection quiet. That’s it for this block:

elif [[ $1 == git-* ]]; then
        ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no git@localhost $*

I am going to go over the last two pieces in reverse order, because it is easier to follow.

If the user sent some SSH commands that were not native git commands, we need to process those commands. So, we will execute the remaining commands in a new bash process.

BUT, what if the user is trying to perform a gitolite command (such as expand or an ADC), what should we do? We can’t just trust that all commands the user sends will either be a git command or a gitolite command, so we need to set up some more black magic to handle that for us. So the first thing we will do when we open a new bash process to handle the remaining commands is source this custom script ($0) with a custom value we made up (SET_ENV_ONLY). That way, if this script executes again we will know it was done by ourselves and we can set up some more magic.

else
        /bin/bash -c "source $0 shiftme SET_ENV_ONLY; $*"

Finally for the last block.

If our new script is being executed, and the first argument is SET_ENV_ONLY (after _shift_ing), then it is because we sourced it from our new bash process from the previous step. What we do now is set up a gitolite command which does something very similar to what happens if the user is performing a native git command: it opens an SSH connection to git@host and performs the gitolite commands.

After this has been done, “gitolite expand” is the same as “ssh git@git.company.com expand”. This makes it so the developer can still run gitolite commands and ADCs, he just has to prefix each command with gitolite. Something like this:

$ ssh [DEVELOPER]@git.company.com "gitolite expand; echo $PATH; gitolite info"

is equivalent to this:

$ ssh git@git.company.com expand
$ ssh [DEVELOPER]@git.company.com echo $PATH
$ ssh git@git.company.com info

Now you just need to create accounts that use this script as their shell. Each user can add whatever public keys they want to their authorized_keys file, or they can authenticate solely with their password. The only requirement is that their server account has a private / public key generated, and that the public key is added to gitolite.

$ ssh root@git.company.com
$ useradd -s /usr/local/bin/custom_gitbash.sh -m [DEVELOPER]
$ su [DEVELOPER]
$ ssh-keygen -t rsa
$ cp ~/.ssh/id_rsa.pub ~/[DEVELOPER]@git.company.com.pub
# add the new public key to the gitolite keydir