alpha
Login
or
Join now
dunkirk.sh
/
core
forked from
tangled.org/core
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
update repoguard to work with rbac
author
Akshay
date
1 year ago
(Feb 4, 2025, 6:51 PM UTC)
commit
8e547e2d
8e547e2d3870987e2bbde31884b5e899f23bfae9
parent
5b4d31b0
5b4d31b081d6d636a95b1ebe8dcf24e8ad77f614
+107
-20
5 changed files
Expand all
Collapse all
Unified
Split
cmd
knotserver
main.go
repoguard
main.go
knotserver
config
config.go
internal.go
rbac
rbac.go
+7
-1
cmd/knotserver/main.go
Reviewed
···
46
46
l.Error("failed to setup server", "error", err)
47
47
return
48
48
}
49
49
-
50
49
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
51
50
51
51
+
imux := knotserver.Internal(ctx, db, e)
52
52
+
iaddr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.InternalPort)
53
53
+
54
54
+
l.Info("starting internal server", "address", iaddr)
55
55
+
go http.ListenAndServe(iaddr, imux)
56
56
+
52
57
l.Info("starting main server", "address", addr)
53
58
l.Error("server error", "error", http.ListenAndServe(addr, mux))
59
59
+
54
60
return
55
61
}
+44
-15
cmd/repoguard/main.go
Reviewed
···
5
5
"flag"
6
6
"fmt"
7
7
"log"
8
8
+
"net/http"
9
9
+
"net/url"
8
10
"os"
9
11
"os/exec"
10
12
"path"
···
21
23
clientIP string
22
24
23
25
// Command line flags
24
24
-
allowedUser = flag.String("user", "", "Allowed git user")
25
25
-
baseDirFlag = flag.String("base-dir", "/home/git", "Base directory for git repositories")
26
26
-
logPathFlag = flag.String("log-path", "/var/log/git-wrapper.log", "Path to log file")
26
26
+
incomingUser = flag.String("user", "", "Allowed git user")
27
27
+
baseDirFlag = flag.String("base-dir", "/home/git", "Base directory for git repositories")
28
28
+
logPathFlag = flag.String("log-path", "/var/log/git-wrapper.log", "Path to log file")
29
29
+
endpoint = flag.String("internal-api", "http://localhost:5555", "Internal API endpoint")
27
30
)
28
31
29
32
func main() {
···
40
43
}
41
44
}
42
45
43
43
-
if *allowedUser == "" {
46
46
+
if *incomingUser == "" {
44
47
exitWithLog("access denied: no user specified")
45
48
}
46
49
47
50
sshCommand := os.Getenv("SSH_ORIGINAL_COMMAND")
48
51
49
52
logEvent("Connection attempt", map[string]interface{}{
50
50
-
"user": *allowedUser,
53
53
+
"user": *incomingUser,
51
54
"command": sshCommand,
52
55
"client": clientIP,
53
56
})
···
63
66
64
67
gitCommand := cmdParts[0]
65
68
66
66
-
// example.com/repo
67
67
-
handlePath := strings.Trim(cmdParts[1], "'")
68
68
-
repoName := handleToDid(handlePath)
69
69
+
// did:foo/repo-name or
70
70
+
// handle/repo-name
71
71
+
components := filepath.SplitList(cmdParts[2])
72
72
+
if len(components) != 2 {
73
73
+
exitWithLog("invalid repo format, needs <user>/<repo>")
74
74
+
}
75
75
+
76
76
+
didOrHandle := components[0]
77
77
+
did := resolveToDid(didOrHandle)
78
78
+
repoName := components[1]
79
79
+
qualifiedRepoName := filepath.Join(did, repoName)
69
80
70
81
validCommands := map[string]bool{
71
82
"git-receive-pack": true,
···
76
87
exitWithLog("access denied: invalid git command")
77
88
}
78
89
79
79
-
did := path.Dir(repoName)
80
90
if gitCommand != "git-upload-pack" {
81
81
-
if !isAllowedUser(*allowedUser, did) {
91
91
+
if !isPushPermitted(*incomingUser, qualifiedRepoName) {
82
92
exitWithLog("access denied: user not allowed")
83
93
}
84
94
}
85
95
86
86
-
fullPath := filepath.Join(*baseDirFlag, repoName)
96
96
+
fullPath := filepath.Join(*baseDirFlag, qualifiedRepoName)
87
97
fullPath = filepath.Clean(fullPath)
88
98
89
99
logEvent("Processing command", map[string]interface{}{
90
90
-
"user": *allowedUser,
100
100
+
"user": *incomingUser,
91
101
"command": gitCommand,
92
102
"repo": repoName,
93
103
"fullPath": fullPath,
···
104
114
}
105
115
106
116
logEvent("Command completed", map[string]interface{}{
107
107
-
"user": *allowedUser,
117
117
+
"user": *incomingUser,
108
118
"command": gitCommand,
109
119
"repo": repoName,
110
120
"success": true,
111
121
})
122
122
+
}
123
123
+
124
124
+
func resolveToDid(didOrHandle string) string {
125
125
+
ident, err := auth.ResolveIdent(context.Background(), didOrHandle)
126
126
+
if err != nil {
127
127
+
exitWithLog(fmt.Sprintf("error resolving handle: %v", err))
128
128
+
}
129
129
+
130
130
+
// did:plc:foobarbaz/repo
131
131
+
return ident.DID.String()
112
132
}
113
133
114
134
func handleToDid(handlePath string) string {
···
166
186
}
167
187
}
168
188
169
169
-
func isAllowedUser(user, did string) bool {
170
170
-
return user == did
189
189
+
func isPushPermitted(user, qualifiedRepoName string) bool {
190
190
+
url, _ := url.Parse(*endpoint + "/push-allowed/")
191
191
+
url.Query().Add(user, user)
192
192
+
url.Query().Add(user, qualifiedRepoName)
193
193
+
194
194
+
req, err := http.Get(url.String())
195
195
+
if err != nil {
196
196
+
exitWithLog(fmt.Sprintf("error verifying permissions: %v", err))
197
197
+
}
198
198
+
199
199
+
return req.StatusCode == http.StatusNoContent
171
200
}
+5
-4
knotserver/config/config.go
Reviewed
···
13
13
}
14
14
15
15
type Server struct {
16
16
-
Host string `env:"HOST, default=0.0.0.0"`
17
17
-
Port int `env:"PORT, default=5555"`
18
18
-
Secret string `env:"SECRET, required"`
19
19
-
DBPath string `env:"DB_PATH, default=knotserver.db"`
16
16
+
Host string `env:"HOST, default=0.0.0.0"`
17
17
+
Port int `env:"PORT, default=5555"`
18
18
+
InternalPort int `env:"PORT, default=5444"`
19
19
+
Secret string `env:"SECRET, required"`
20
20
+
DBPath string `env:"DB_PATH, default=knotserver.db"`
20
21
// This disables signature verification so use with caution.
21
22
Dev bool `env:"DEV, default=false"`
22
23
}
+47
knotserver/internal.go
Reviewed
···
1
1
+
package knotserver
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"net/http"
6
6
+
7
7
+
"github.com/go-chi/chi/v5"
8
8
+
"github.com/sotangled/tangled/knotserver/db"
9
9
+
"github.com/sotangled/tangled/rbac"
10
10
+
)
11
11
+
12
12
+
type InternalHandle struct {
13
13
+
db *db.DB
14
14
+
e *rbac.Enforcer
15
15
+
}
16
16
+
17
17
+
func (h *InternalHandle) PushAllowed(w http.ResponseWriter, r *http.Request) {
18
18
+
user := r.URL.Query().Get("user")
19
19
+
repo := r.URL.Query().Get("repo")
20
20
+
21
21
+
if user == "" || repo == "" {
22
22
+
w.WriteHeader(http.StatusBadRequest)
23
23
+
return
24
24
+
}
25
25
+
26
26
+
ok, err := h.e.IsPushAllowed(user, ThisServer, repo)
27
27
+
if err != nil || !ok {
28
28
+
w.WriteHeader(http.StatusForbidden)
29
29
+
return
30
30
+
}
31
31
+
32
32
+
w.WriteHeader(http.StatusNoContent)
33
33
+
return
34
34
+
}
35
35
+
36
36
+
func Internal(ctx context.Context, db *db.DB, e *rbac.Enforcer) http.Handler {
37
37
+
r := chi.NewRouter()
38
38
+
39
39
+
h := InternalHandle{
40
40
+
db,
41
41
+
e,
42
42
+
}
43
43
+
44
44
+
r.Get("/push-allowed", h.PushAllowed)
45
45
+
46
46
+
return r
47
47
+
}
+4
rbac/rbac.go
Reviewed
···
131
131
return e.isRole(user, "server:member", domain)
132
132
}
133
133
134
134
+
func (e *Enforcer) IsPushAllowed(user, domain, repo string) (bool, error) {
135
135
+
return e.E.Enforce(user, domain, repo, "repo:push")
136
136
+
}
137
137
+
134
138
// keyMatch2Func is a wrapper for keyMatch2 to make it compatible with Casbin
135
139
func keyMatch2Func(args ...interface{}) (interface{}, error) {
136
140
name1 := args[0].(string)