aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorLibravatar Rutger Broekhoff2023-12-30 15:28:56 +0100
committerLibravatar Rutger Broekhoff2023-12-30 15:28:56 +0100
commit1ec40a20de02ce6ba873271b6838cb72e999612f (patch)
treea00ae419e575597ed9a69985a1cf4cd8ab3a1bba /cmd
parentf6c92c5e2d87ab1334648b0d1293771de7aae4a5 (diff)
downloadgitolfs3-1ec40a20de02ce6ba873271b6838cb72e999612f.tar.gz
gitolfs3-1ec40a20de02ce6ba873271b6838cb72e999612f.zip
Improve git-lfs-authenticate
Diffstat (limited to 'cmd')
-rw-r--r--cmd/git-lfs-authenticate/main.go80
1 files changed, 70 insertions, 10 deletions
diff --git a/cmd/git-lfs-authenticate/main.go b/cmd/git-lfs-authenticate/main.go
index 56b9d78..e4fa67d 100644
--- a/cmd/git-lfs-authenticate/main.go
+++ b/cmd/git-lfs-authenticate/main.go
@@ -7,14 +7,57 @@ import (
7 "encoding/json" 7 "encoding/json"
8 "errors" 8 "errors"
9 "fmt" 9 "fmt"
10 "io"
10 "os" 11 "os"
11 "os/exec" 12 "os/exec"
12 "strings" 13 "strings"
14 "sync"
15 "sync/atomic"
13 "time" 16 "time"
14 17
15 "github.com/golang-jwt/jwt/v5" 18 "github.com/golang-jwt/jwt/v5"
19 "github.com/rs/xid"
16) 20)
17 21
22type logger struct {
23 reqID string
24 time time.Time
25 m sync.Mutex
26 // Contained value must implement io.WriteCloser
27 wc atomic.Value
28}
29
30func newLogger(reqID string) *logger {
31 return &logger{reqID: reqID, time: time.Now()}
32}
33
34func (l *logger) writer() io.WriteCloser {
35 w := l.wc.Load()
36 if w == nil {
37 l.m.Lock()
38 if l.wc.Load() == nil {
39 os.MkdirAll(".gitolfs3/logs/", 0o600) // drw-------
40 path := fmt.Sprintf(".gitolfs3/logs/gitolfs3-%s-%s.log", l.time, l.reqID)
41 var err error
42 if w, err = os.Create(path); err == nil {
43 l.wc.Store(w)
44 }
45 }
46 l.m.Unlock()
47 }
48 return w.(io.WriteCloser)
49}
50
51func (l *logger) logf(msg string, args ...any) {
52 fmt.Fprintf(l.writer(), msg, args...)
53}
54
55func (l *logger) close() {
56 if wc := l.wc.Load(); wc != nil {
57 wc.(io.Closer).Close()
58 }
59}
60
18func die(msg string, args ...any) { 61func die(msg string, args ...any) {
19 fmt.Fprint(os.Stderr, "Error: ") 62 fmt.Fprint(os.Stderr, "Error: ")
20 fmt.Fprintf(os.Stderr, msg, args...) 63 fmt.Fprintf(os.Stderr, msg, args...)
@@ -22,14 +65,22 @@ func die(msg string, args ...any) {
22 os.Exit(1) 65 os.Exit(1)
23} 66}
24 67
25func getGitoliteAccess(path, user, gitolitePerm string) bool { 68func dieReqID(reqID string, msg string, args ...any) {
69 fmt.Fprint(os.Stderr, "Error: ")
70 fmt.Fprintf(os.Stderr, msg, args...)
71 fmt.Fprintf(os.Stderr, "(request ID: %s)\n", reqID)
72 os.Exit(1)
73}
74
75func getGitoliteAccess(logger *logger, reqID, path, user, gitolitePerm string) bool {
26 // gitolite access -q: returns only exit code 76 // gitolite access -q: returns only exit code
27 cmd := exec.Command("gitolite", "access", "-q", path, user, gitolitePerm) 77 cmd := exec.Command("gitolite", "access", "-q", path, user, gitolitePerm)
28 err := cmd.Run() 78 err := cmd.Run()
29 permGranted := err == nil 79 permGranted := err == nil
30 var exitErr *exec.ExitError 80 var exitErr *exec.ExitError
31 if err != nil && !errors.As(err, &exitErr) { 81 if err != nil && !errors.As(err, &exitErr) {
32 die("failed to query access information") 82 logger.logf("Failed to query access information (%s): %s", cmd, err)
83 dieReqID(reqID, "failed to query access information")
33 } 84 }
34 return permGranted 85 return permGranted
35} 86}
@@ -66,6 +117,9 @@ func main() {
66 // code and print the error message in plain text to standard error. See 117 // code and print the error message in plain text to standard error. See
67 // https://github.com/git-lfs/git-lfs/blob/baf40ac99850a62fe98515175d52df5c513463ec/lfshttp/ssh.go#L76-L117 118 // https://github.com/git-lfs/git-lfs/blob/baf40ac99850a62fe98515175d52df5c513463ec/lfshttp/ssh.go#L76-L117
68 119
120 reqID := xid.New().String()
121 logger := newLogger(reqID)
122
69 if len(os.Args) != 3 { 123 if len(os.Args) != 3 {
70 die("expected 2 arguments (path, operation), got %d", len(os.Args)-1) 124 die("expected 2 arguments (path, operation), got %d", len(os.Args)-1)
71 } 125 }
@@ -78,35 +132,40 @@ func main() {
78 132
79 user := os.Getenv("GL_USER") 133 user := os.Getenv("GL_USER")
80 if user == "" { 134 if user == "" {
81 die("internal error") 135 logger.logf("Environment variable GL_USER is not set")
136 dieReqID(reqID, "internal error")
82 } 137 }
83 keyPath := os.Getenv("GITOLFS3_KEY_PATH") 138 keyPath := os.Getenv("GITOLFS3_KEY_PATH")
84 if keyPath == "" { 139 if keyPath == "" {
85 die("internal error") 140 logger.logf("Environment variable GITOLFS3_KEY_PATH is not set")
141 dieReqID(reqID, "internal error")
86 } 142 }
87 keyStr, err := os.ReadFile(keyPath) 143 keyStr, err := os.ReadFile(keyPath)
88 if err != nil { 144 if err != nil {
89 die("internal error") 145 logger.logf("Cannot read key in GITOLFS3_KEY_PATH: %s", err)
146 dieReqID(reqID, "internal error")
90 } 147 }
91 keyStr = bytes.TrimSpace(keyStr) 148 keyStr = bytes.TrimSpace(keyStr)
92 defer wipe(keyStr) 149 defer wipe(keyStr)
93 150
94 if hex.DecodedLen(len(keyStr)) != ed25519.SeedSize { 151 if hex.DecodedLen(len(keyStr)) != ed25519.SeedSize {
95 die("internal error") 152 logger.logf("Fatal: provided private key (seed) is invalid: does not have expected length")
153 dieReqID(reqID, "internal error")
96 } 154 }
97 155
98 seed := make([]byte, ed25519.SeedSize) 156 seed := make([]byte, ed25519.SeedSize)
99 defer wipe(seed) 157 defer wipe(seed)
100 if _, err = hex.Decode(seed, keyStr); err != nil { 158 if _, err = hex.Decode(seed, keyStr); err != nil {
101 die("internal error") 159 logger.logf("Fatal: cannot decode provided private key (seed): %s", err)
160 dieReqID(reqID, "internal error")
102 } 161 }
103 privateKey := ed25519.NewKeyFromSeed(seed) 162 privateKey := ed25519.NewKeyFromSeed(seed)
104 163
105 if !getGitoliteAccess(path, user, "R") { 164 if !getGitoliteAccess(logger, reqID, path, user, "R") {
106 die("repository not found") 165 die("repository not found")
107 } 166 }
108 if operation == "upload" && !getGitoliteAccess(path, user, "W") { 167 if operation == "upload" && !getGitoliteAccess(logger, reqID, path, user, "W") {
109 // User has read access but not write access 168 // User has read access but no write access
110 die("forbidden") 169 die("forbidden")
111 } 170 }
112 171
@@ -126,6 +185,7 @@ func main() {
126 token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims) 185 token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
127 ss, err := token.SignedString(privateKey) 186 ss, err := token.SignedString(privateKey)
128 if err != nil { 187 if err != nil {
188 logger.logf("Fatal: failed to generate JWT: %s", err)
129 die("failed to generate token") 189 die("failed to generate token")
130 } 190 }
131 191