diff options
| author | Max Resnick <max@ofmax.li> | 2022-11-09 22:35:03 -0800 |
|---|---|---|
| committer | Max Resnick <max@ofmax.li> | 2022-11-09 22:35:03 -0800 |
| commit | c55145f51542f2409c2822a60f99f9e3208214df (patch) | |
| tree | 253e07ac07ce4134b54841e6ae51836492edf987 | |
| parent | e3010e016c716d3f95936752846550e47771cd51 (diff) | |
| download | go-git-server-c55145f51542f2409c2822a60f99f9e3208214df.tar.gz | |
add authz setup
| -rw-r--r-- | go.mod | 1 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | main.go | 66 |
3 files changed, 54 insertions, 15 deletions
@@ -6,5 +6,6 @@ require golang.org/x/crypto v0.0.0-20221012134737-56aed061732a require ( github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/casbin/casbin v1.9.1 // indirect github.com/casbin/casbin/v2 v2.56.0 // indirect ) @@ -1,5 +1,7 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM= +github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog= github.com/casbin/casbin/v2 v2.56.0 h1:4qM+hDfj+i9M6lBbguafWKE/8tJA+9vRY5+l0ZB5WTo= github.com/casbin/casbin/v2 v2.56.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/rand" "encoding/base64" "encoding/csv" @@ -12,17 +13,18 @@ import ( "net/http/cgi" "os" + "github.com/casbin/casbin/v2" "golang.org/x/crypto/bcrypt" ) var ( - reposDir = flag.String("r", ".", "Directory containing git repositories") + reposDir = flag.String("r", "./repos", "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{} ) +// LoadTokens load tokens from a csv into a map func LoadTokens() (map[string]string, error) { tokenMap := make(map[string]string) contents, err := os.Open("tokens.csv") @@ -44,6 +46,7 @@ func LoadTokens() (map[string]string, error) { return tokenMap, err } +// NewToken generate a new token func NewToken() (string, string, error) { tokenBytes := make([]byte, 28) for i := range tokenBytes { @@ -65,19 +68,26 @@ func NewToken() (string, string, error) { // 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", - }, - } + projectDirEnv := fmt.Sprintf("GIT_PROJECT_ROOT=%v", reposDir) + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + ctx := req.Context() + uid := ctx.Value("urn") + git := &cgi.Handler{ + Path: "/bin/sh", + Args: []string{"-c", backendCommand}, + Dir: ".", + Env: []string{ + projectDirEnv, + "GIT_HTTP_EXPORT_ALL=1", + fmt.Sprintf("REMOTE_USER=%s", uid), + }, + } + git.ServeHTTP(rw, req) + }) } // Authentication middleware to enforce authentication of all requests. -func Authentication(next http.Handler) http.Handler { +func Authentication(authMap map[string]string, next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { u, p, ok := req.BasicAuth() if !ok { @@ -85,7 +95,8 @@ func Authentication(next http.Handler) http.Handler { http.Error(rw, "Authentication Required", 401) return } - hash, ok := authMap[fmt.Sprintf("user:%s", u)] + urn := fmt.Sprintf("uid:%s", u) + hash, ok := authMap[urn] if !ok { http.Error(rw, "Bad Request", 400) return @@ -99,10 +110,36 @@ func Authentication(next http.Handler) http.Handler { http.Error(rw, "Bad Request", 400) return } + ctx := context.WithValue(req.Context(), "urn", urn) + next.ServeHTTP(rw, req.WithContext(ctx)) + }) +} + +func Authorization(enf *casbin.Enforcer, next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + ctx := req.Context() + urn := ctx.Value("urn") + repo := req.URL.Path + action := req.Method + // defer req.Body.Close() + // body, _ := io.ReadAll(req.Body) + // log.Printf("%s", body) + ok, err := enf.Enforce(urn, repo, action) + if err != nil { + log.Printf("error running enforce %s", err) + http.Error(rw, "Bad Request", http.StatusBadRequest) + } + if !ok { + log.Printf("Access denied") + http.Error(rw, "Access denied", http.StatusUnauthorized) + } + log.Printf("Method %s Url %s", action, repo) + next.ServeHTTP(rw, req.WithContext(ctx)) }) } func main() { + enf, _ := casbin.NewEnforcer("./auth_model.ini", "./policy.csv") flag.Parse() if *newToken { token, hash, err := NewToken() @@ -118,9 +155,8 @@ func main() { } 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) + mux := Authentication(tokens, Authorization(enf, router)) log.Fatal(http.ListenAndServe(":8080", mux)) } |