基於golang+openssh 服務實現一個簡單的git over ssh 服務
昨天看了開源的codefever 以及以前簡單學習過gogs,剛才學習下git over ssh 的實現機制
基於openssh + golang (golang 部分參考了gogs 處理)實現了一個簡單的git server (ssh 協議的)
原理說明
核心還是我們的openssh server 建立一個git 賬戶,此賬戶使用了authorized_keys的forcecommand 功能
forcecommand 中我們添加了git 的處理
- forcecommand 程式碼
package main
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
)
func init() {
file := "./" + "message" + ".txt"
logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
if err != nil {
panic(err)
}
log.SetOutput(logFile)
log.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC)
}
func parseSSHCmd(cmd string) (string, string) {
ss := strings.SplitN(cmd, " ", 2)
if len(ss) != 2 {
return "", ""
}
return ss[0], strings.Replace(ss[1], "'/", "'", 1)
}
func main() {
// forcecommand 會包含一個SSH_ORIGINAL_COMMAND 裡邊是git 的一些操作命令,包含了具體命令的處理
println(os.Getenv("SSH_ORIGINAL_COMMAND"))
sshCmd := os.Getenv("SSH_ORIGINAL_COMMAND")
verb, args := parseSSHCmd(sshCmd)
repoFullName := strings.ToLower(strings.Trim(args, "'"))
verbs := strings.Split(verb, " ")
var gitCmd *exec.Cmd
gitRepoPath := fmt.Sprintf("/opt/gitrepo/%s", repoFullName)
os.Setenv("MY_UID", "dalongrong")
if len(verb) == 2 {
println(verbs[0], verbs[1], gitRepoPath)
gitCmd = exec.Command(verbs[0], verbs[1], gitRepoPath)
} else {
println(verbs[0], gitRepoPath)
gitCmd = exec.Command(verb, gitRepoPath)
}
gitCmd.Stdin = os.Stdin
gitCmd.Stdout = os.Stdout
gitCmd.Stderr = os.Stderr
if err := gitCmd.Run(); err != nil {
log.Fatal("Internal error", "Failed to execute git command: %v", err)
}
return
}
docker 構建
FROM golang:1.17-alpine AS build-env
WORKDIR /go/src/app
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn
COPY . .
RUN apk update && apk add git \
&& go build -o git-shell
FROM alpine:latest
WORKDIR /app
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
COPY --from=build-env /go/src/app/git-shell /app/git-shell
- authorized_keys 配置格式
目前比較簡單,沒有進行復雜的校驗處理
command 部分使用了自己開發的git 處理
command="/opt/git-shell",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <ssh public key>
- openssh 服務啟動
基於docker-compose 執行
version: '3'
services:
ssh:
image: dalongrong/openssh-server
build: ./
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- SUDO_ACCESS=true #optional
- PASSWORD_ACCESS=true #optional
- USER_PASSWORD=dalongdemo #optional
- USER_NAME=git #optional
volumes:
- ./config:/config
- ./gitrepo:/opt/gitrepo
ports:
- "2222:2222"
使用說明
- 建立bare git repo
啟動之後我們需要建立git repo 注意是bare 模式的
進入openssh 容器
su git
cd /opt/gitrepo
git init --bare demoapp2.git
效果
- 新增ssh 認證客戶
config的.ssh 資料夾中的authorized_keys 格式如上 - clone 程式碼
git clone ssh://git@localhost:2222/demoapp2.git demoapp2
效果
push 程式碼
cd demoapp2
touch rong.txt
git add --all
git commit -m "add"
git push
效果
說明
如果有許可權的問題就可能需要設定先許可權了(openssh 容器內執行)
chmod 0755 /opt/gitrepo /config
chmod 700 /config/.ssh
以上是一個簡單的學習,參考了gogs、gitlab 等開源專案,基於此剛才對於git ssh server 就有了一個比較完整的瞭解
完整程式碼已經放github 了,大家可以參考,實際上目前一些開源的git 支援ssh 協議的基本都是這個套路,只是authorized_keys
的forcecommand 處理上大家好多是不一樣的,而且新版本的gitlab 已經自己寫了一個ssh server,同時推薦基於AuthorizedKeysCommand
更好的優化處ssh key 的處理了
參考資料
https://github.com/rongfengliang/write-one-git-ssh-server
https://github.com/gogs/gogs
https://github.com/PGYER/codefever
https://git-scm.com/docs/git-receive-pack
https://git-scm.com/docs/git-upload-pack
https://www.ssh.com/academy/ssh/authorized_keys/openssh
https://blog.scalesec.com/just-in-time-ssh-provisioning-7b20d9736a07
https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE
https://git-scm.com/docs/pack-protocol/2.2.3