package main import ( "crypto/rand" "encoding/base64" "encoding/csv" "flag" "fmt" "log" "math/big" "net/http" "net/http/cgi" "os" "golang.org/x/crypto/bcrypt" ) var ( reposDir = flag.String("r", ".", "Directory containing git repositories") backendCommand = flag.String("c", "git http-backend", "CGI binary to execute") addr = flag.String("l", ":8080", "Address/port to listen on") newToken = flag.Bool("t", false, "make a new token") authMap = map[string]string{} ) func LoadTokens() (map[string]string, error) { tokenMap := make(map[string]string) contents, err := os.Open("tokens.csv") if err != nil { fmt.Println("File reading error", err) return tokenMap, err } defer contents.Close() r := csv.NewReader(contents) tokens, err := r.ReadAll() if err != nil { fmt.Println("File reading error", err) return tokenMap, err } for _, acctToken := range tokens { acct, hash := acctToken[0], acctToken[1] tokenMap[acct] = hash } return tokenMap, err } func NewToken() (string, string, error) { tokenBytes := make([]byte, 28) for i := range tokenBytes { max := big.NewInt(int64(255)) randInt, err := rand.Int(rand.Reader, max) if err != nil { return "", "", err } tokenBytes[i] = uint8(randInt.Int64()) } hashBytes, err := bcrypt.GenerateFromPassword(tokenBytes, bcrypt.DefaultCost) if err != nil { return "", "", err } token := base64.URLEncoding.EncodeToString(tokenBytes) hash := string(hashBytes) return token, hash, nil } type Handler struct { cgiHandler *cgi.Handler } func NewHandler(reposDir, backendCommand string) *Handler { return &Handler{ &cgi.Handler{ Path: "/bin/sh", Args: []string{"-c", backendCommand}, Dir: ".", Env: []string{ fmt.Sprintf("GIT_PROJECT_ROOT=%v", reposDir), "GIT_HTTP_EXPORT_ALL=1", }, }, } } func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { u, p, ok := req.BasicAuth() if !ok { rw.Header().Set("WWW-Authenticate", `Basic realm="git"`) http.Error(rw, "Authentication Required", 401) return } hash, ok := authMap[fmt.Sprintf("user:%s", u)] if !ok { http.Error(rw, "Bad Request", 400) return } token, err := base64.URLEncoding.DecodeString(p) if err != nil { http.Error(rw, "Bad Request", 400) return } if err := bcrypt.CompareHashAndPassword([]byte(hash), token); err != nil { http.Error(rw, "Bad Request", 400) return } h.cgiHandler.ServeHTTP(rw, req) } func main() { flag.Parse() if *newToken { token, hash, err := NewToken() if err != nil { log.Fatal(err) } fmt.Printf("token: %s\nhash: %s\n", token, hash) return } tokens, err := LoadTokens() fmt.Println(tokens) if err != nil { log.Fatal(err) } authMap = tokens http.Handle("/", NewHandler(*reposDir, *backendCommand)) log.Fatal(http.ListenAndServe(":8080", nil)) }