tangled
alpha
login
or
join now
tjh.dev
/
core
forked from
tangled.org/core
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
repoguard: init
anirudh.fi
1 year ago
d90bef31
4ae73d9e
verified
This commit was signed with the committer's
known signature
.
anirudh.fi
SSH Key Fingerprint:
SHA256:FQUiBXeyBQT4WKOm7EKh6hLkHjBh9MdfkV3my0dueGE=
+148
1 changed file
expand all
collapse all
unified
split
cmd
repoguard
main.go
+148
cmd/repoguard/main.go
···
1
1
+
package main
2
2
+
3
3
+
import (
4
4
+
"flag"
5
5
+
"fmt"
6
6
+
"log"
7
7
+
"os"
8
8
+
"os/exec"
9
9
+
"path/filepath"
10
10
+
"strings"
11
11
+
"time"
12
12
+
)
13
13
+
14
14
+
var (
15
15
+
logger *log.Logger
16
16
+
logFile *os.File
17
17
+
clientIP string
18
18
+
19
19
+
// Command line flags
20
20
+
allowedUser = flag.String("user", "", "Allowed git user")
21
21
+
baseDirFlag = flag.String("base-dir", "/home/git", "Base directory for git repositories")
22
22
+
logPathFlag = flag.String("log-path", "/var/log/git-wrapper.log", "Path to log file")
23
23
+
)
24
24
+
25
25
+
func main() {
26
26
+
flag.Parse()
27
27
+
28
28
+
defer cleanup()
29
29
+
initLogger()
30
30
+
31
31
+
// Get client IP from SSH environment
32
32
+
if connInfo := os.Getenv("SSH_CONNECTION"); connInfo != "" {
33
33
+
parts := strings.Fields(connInfo)
34
34
+
if len(parts) > 0 {
35
35
+
clientIP = parts[0]
36
36
+
}
37
37
+
}
38
38
+
39
39
+
if *allowedUser == "" {
40
40
+
exitWithLog("access denied: no user specified")
41
41
+
}
42
42
+
43
43
+
sshCommand := os.Getenv("SSH_ORIGINAL_COMMAND")
44
44
+
45
45
+
logEvent("Connection attempt", map[string]interface{}{
46
46
+
"user": *allowedUser,
47
47
+
"command": sshCommand,
48
48
+
"client": clientIP,
49
49
+
})
50
50
+
51
51
+
if sshCommand == "" {
52
52
+
exitWithLog("access denied: no ssh command provided")
53
53
+
}
54
54
+
55
55
+
cmdParts := strings.Fields(sshCommand)
56
56
+
if len(cmdParts) < 2 {
57
57
+
exitWithLog("invalid command format")
58
58
+
}
59
59
+
60
60
+
gitCommand := cmdParts[0]
61
61
+
repoName := strings.Trim(cmdParts[1], "'")
62
62
+
63
63
+
validCommands := map[string]bool{
64
64
+
"git-receive-pack": true,
65
65
+
"git-upload-pack": true,
66
66
+
"git-upload-archive": true,
67
67
+
}
68
68
+
if !validCommands[gitCommand] {
69
69
+
exitWithLog("access denied: invalid git command")
70
70
+
}
71
71
+
72
72
+
if !isAllowedUser(*allowedUser, repoName) {
73
73
+
exitWithLog("access denied: user not allowed")
74
74
+
}
75
75
+
76
76
+
fullPath := filepath.Join(*baseDirFlag, repoName)
77
77
+
fullPath = filepath.Clean(fullPath)
78
78
+
79
79
+
logEvent("Processing command", map[string]interface{}{
80
80
+
"user": *allowedUser,
81
81
+
"command": gitCommand,
82
82
+
"repo": repoName,
83
83
+
"fullPath": fullPath,
84
84
+
"client": clientIP,
85
85
+
})
86
86
+
87
87
+
cmd := exec.Command(gitCommand, fullPath)
88
88
+
cmd.Stdout = os.Stdout
89
89
+
cmd.Stderr = os.Stderr
90
90
+
cmd.Stdin = os.Stdin
91
91
+
92
92
+
if err := cmd.Run(); err != nil {
93
93
+
exitWithLog(fmt.Sprintf("command failed: %v", err))
94
94
+
}
95
95
+
96
96
+
logEvent("Command completed", map[string]interface{}{
97
97
+
"user": *allowedUser,
98
98
+
"command": gitCommand,
99
99
+
"repo": repoName,
100
100
+
"success": true,
101
101
+
})
102
102
+
}
103
103
+
104
104
+
func initLogger() {
105
105
+
var err error
106
106
+
logFile, err = os.OpenFile(*logPathFlag, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
107
107
+
if err != nil {
108
108
+
fmt.Fprintf(os.Stderr, "failed to open log file: %v\n", err)
109
109
+
os.Exit(1)
110
110
+
}
111
111
+
112
112
+
logger = log.New(logFile, "", 0)
113
113
+
}
114
114
+
115
115
+
func logEvent(event string, fields map[string]interface{}) {
116
116
+
entry := fmt.Sprintf(
117
117
+
"timestamp=%q event=%q",
118
118
+
time.Now().Format(time.RFC3339),
119
119
+
event,
120
120
+
)
121
121
+
122
122
+
for k, v := range fields {
123
123
+
entry += fmt.Sprintf(" %s=%q", k, v)
124
124
+
}
125
125
+
126
126
+
logger.Println(entry)
127
127
+
}
128
128
+
129
129
+
func exitWithLog(message string) {
130
130
+
logEvent("Access denied", map[string]interface{}{
131
131
+
"error": message,
132
132
+
})
133
133
+
logFile.Sync()
134
134
+
fmt.Fprintf(os.Stderr, "error: %s\n", message)
135
135
+
os.Exit(1)
136
136
+
}
137
137
+
138
138
+
func cleanup() {
139
139
+
if logFile != nil {
140
140
+
logFile.Sync()
141
141
+
logFile.Close()
142
142
+
}
143
143
+
}
144
144
+
145
145
+
func isAllowedUser(user, repoPath string) bool {
146
146
+
pathUser := strings.Split(repoPath, "/")[0]
147
147
+
return pathUser == user
148
148
+
}