aboutsummaryrefslogtreecommitdiff
path: root/main.go
blob: e786c9b97a028a67b7808d4eb09184bba4174fc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
}

// GitHttpBackendHandler a handler for git cgi
func GitHttpBackendHandler(reposDir, backendCommand string) http.Handler {
	return &cgi.Handler{
		Path: "/bin/sh",
		Args: []string{"-c", backendCommand},
		Dir:  ".",
		Env: []string{
			fmt.Sprintf("GIT_PROJECT_ROOT=%v", reposDir),
			"GIT_HTTP_EXPORT_ALL=1",
		},
	}
}

// Authentication middleware to enforce authentication of all requests.
func Authentication(next http.Handler) http.Handler {
	return http.HandlerFunc(func(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
		}
	})
}

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()
	if err != nil {
		log.Fatal(err)
	}
	router := http.NewServeMux()
	// TODO we don't want to use a global
	authMap = tokens
	// de-reference args
	router.Handle("/", GitHttpBackendHandler(*reposDir, *backendCommand))
	mux := Authentication(router)
	log.Fatal(http.ListenAndServe(":8080", mux))
}