diff --git a/cmd/objgitd/example_hook_test.go b/cmd/objgitd/example_hook_test.go index 6de5198..92175f2 100644 --- a/cmd/objgitd/example_hook_test.go +++ b/cmd/objgitd/example_hook_test.go @@ -13,6 +13,7 @@ import ( "github.com/go-git/go-billy/v6/memfs" "github.com/go-git/go-git/v6/plumbing/transport" + "tangled.org/xeiaso.net/objgit/internal/auth" ) // TestExampleHookRuns pushes the repository's own example hook @@ -38,6 +39,7 @@ func TestExampleHookRuns(t *testing.T) { d := &daemon{ fs: fs, loader: transport.NewFilesystemLoader(fs, false), + authz: auth.AllowAnonymous{AllowWrite: true}, allowPush: true, allowHooks: true, hookTimeout: 30 * time.Second, diff --git a/cmd/objgitd/git_protocol.go b/cmd/objgitd/git_protocol.go index 2532b76..43ef84e 100644 --- a/cmd/objgitd/git_protocol.go +++ b/cmd/objgitd/git_protocol.go @@ -21,6 +21,7 @@ import ( "github.com/go-git/go-git/v6/plumbing/transport" "github.com/go-git/go-git/v6/storage" "github.com/go-git/go-git/v6/storage/filesystem" + "tangled.org/xeiaso.net/objgit/internal/auth" ) // handshakeTimeout bounds how long a client has to send its git-proto-request. @@ -43,10 +44,20 @@ type streamingStorer struct { storage.Storer } +// operationFor maps a git service to the access it needs: receive-pack writes, +// everything else (upload-pack, upload-archive) reads. +func operationFor(service string) auth.Operation { + if service == transport.ReceivePackService { + return auth.Write + } + return auth.Read +} + // daemon serves the git:// (TCP) protocol out of a billy filesystem. type daemon struct { fs billy.Filesystem loader transport.Loader + authz auth.Authorizer allowPush bool // allowHooks gates running .objgit/hooks/receive-pack after a push. @@ -113,6 +124,16 @@ func (d *daemon) handle(ctx context.Context, conn net.Conn) error { // writer is the raw conn: its final Close() ends the connection. r := io.NopCloser(conn) + if d.authz.Authorize(ctx, auth.Request{ + Repo: req.Pathname, + Operation: operationFor(req.RequestCommand), + Cred: auth.Anonymous{}, + Transport: "git", + }) != auth.Allow { + _, _ = pktline.WriteError(conn, fmt.Errorf("access denied")) + return fmt.Errorf("access denied for %q (%s)", req.Pathname, req.RequestCommand) + } + switch req.RequestCommand { case transport.UploadPackService: st, err := d.loader.Load(&url.URL{Path: req.Pathname}) @@ -133,10 +154,6 @@ func (d *daemon) handle(ctx context.Context, conn net.Conn) error { return transport.UploadArchive(ctx, st, r, conn, &transport.UploadArchiveRequest{}) case transport.ReceivePackService: - if !d.allowPush { - _, _ = pktline.WriteError(conn, fmt.Errorf("push is disabled on this server")) - return fmt.Errorf("push rejected for %q", req.Pathname) - } st, err := d.loadOrInit(req.Pathname) if err != nil { _, _ = pktline.WriteError(conn, fmt.Errorf("cannot open repository %q", req.Pathname)) diff --git a/cmd/objgitd/git_protocol_test.go b/cmd/objgitd/git_protocol_test.go index 4c725f8..06cc5b0 100644 --- a/cmd/objgitd/git_protocol_test.go +++ b/cmd/objgitd/git_protocol_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-git/go-billy/v6/memfs" "github.com/go-git/go-git/v6/plumbing/transport" + "tangled.org/xeiaso.net/objgit/internal/auth" ) // TestDaemonPushCreatesRepo reproduces "git push git://host/new.git" against a @@ -26,6 +27,7 @@ func TestDaemonPushCreatesRepo(t *testing.T) { d := &daemon{ fs: fs, loader: transport.NewFilesystemLoader(fs, false), + authz: auth.AllowAnonymous{AllowWrite: true}, allowPush: true, } @@ -83,6 +85,7 @@ func TestDaemonPushDisabled(t *testing.T) { d := &daemon{ fs: fs, loader: transport.NewFilesystemLoader(fs, false), + authz: auth.AllowAnonymous{AllowWrite: false}, allowPush: false, } diff --git a/cmd/objgitd/hooks_test.go b/cmd/objgitd/hooks_test.go index 15e6ce3..1567f34 100644 --- a/cmd/objgitd/hooks_test.go +++ b/cmd/objgitd/hooks_test.go @@ -16,6 +16,7 @@ import ( "github.com/go-git/go-billy/v6/memfs" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/transport" + "tangled.org/xeiaso.net/objgit/internal/auth" ) func TestDiffRefs(t *testing.T) { @@ -107,6 +108,7 @@ func TestReceivePackHook(t *testing.T) { d := &daemon{ fs: fs, loader: transport.NewFilesystemLoader(fs, false), + authz: auth.AllowAnonymous{AllowWrite: true}, allowPush: true, allowHooks: true, hookTimeout: 30 * time.Second, @@ -184,6 +186,7 @@ func TestReceivePackHookAbsent(t *testing.T) { d := &daemon{ fs: fs, loader: transport.NewFilesystemLoader(fs, false), + authz: auth.AllowAnonymous{AllowWrite: true}, allowPush: true, allowHooks: true, hookTimeout: 30 * time.Second, diff --git a/cmd/objgitd/main.go b/cmd/objgitd/main.go index 39afb85..31a1281 100644 --- a/cmd/objgitd/main.go +++ b/cmd/objgitd/main.go @@ -18,6 +18,7 @@ import ( "github.com/tigrisdata/storage-go" "golang.org/x/sync/errgroup" "tangled.org/xeiaso.net/objgit/internal" + "tangled.org/xeiaso.net/objgit/internal/auth" "tangled.org/xeiaso.net/objgit/internal/s3fs" _ "github.com/joho/godotenv/autoload" @@ -73,6 +74,7 @@ func main() { d := &daemon{ fs: fsys, loader: transport.NewFilesystemLoader(fsys, false), + authz: auth.AllowAnonymous{AllowWrite: *allowPush}, allowPush: *allowPush, allowHooks: *allowHooks, hookTimeout: *hookTimeout,