11package main
22
33import (
4+ "bytes"
45 "context"
6+ "crypto/sha256"
7+ "encoding/hex"
8+ "encoding/json"
59 "flag"
610 "fmt"
11+ "hash"
12+ "io"
713 "os"
14+ "plugin"
15+ "strings"
816
917 "github.com/git-ecosystem/git-bundle-server/cmd/utils"
1018 "github.com/git-ecosystem/git-bundle-server/internal/argparse"
19+ auth_internal "github.com/git-ecosystem/git-bundle-server/internal/auth"
1120 "github.com/git-ecosystem/git-bundle-server/internal/log"
21+ "github.com/git-ecosystem/git-bundle-server/pkg/auth"
1222)
1323
24+ func getPluginChecksum (pluginPath string ) (hash.Hash , error ) {
25+ file , err := os .Open (pluginPath )
26+ if err != nil {
27+ return nil , err
28+ }
29+ defer file .Close ()
30+
31+ checksum := sha256 .New ()
32+ if _ , err := io .Copy (checksum , file ); err != nil {
33+ return nil , err
34+ }
35+
36+ return checksum , nil
37+ }
38+
39+ func parseAuthConfig (configPath string ) (auth.AuthMiddleware , error ) {
40+ var config authConfig
41+ fileBytes , err := os .ReadFile (configPath )
42+ if err != nil {
43+ return nil , err
44+ }
45+
46+ err = json .Unmarshal (fileBytes , & config )
47+ if err != nil {
48+ return nil , err
49+ }
50+
51+ switch strings .ToLower (config .AuthMode ) {
52+ case "fixed" :
53+ return auth_internal .NewFixedCredentialAuth (config .Parameters )
54+ case "plugin" :
55+ if len (config .Path ) == 0 {
56+ return nil , fmt .Errorf ("plugin .so is empty" )
57+ }
58+ if len (config .Initializer ) == 0 {
59+ return nil , fmt .Errorf ("plugin initializer symbol is empty" )
60+ }
61+ if len (config .Checksum ) == 0 {
62+ return nil , fmt .Errorf ("SHA256 checksum of plugin file is empty" )
63+ }
64+
65+ // First, verify plugin checksum matches expected
66+ // Note: time-of-check/time-of-use could be exploited here (anywhere
67+ // between the checksum check and invoking the initializer). There's not
68+ // much we can realistically do about that short of rewriting the plugin
69+ // package, so we advise users to carefully control access to their
70+ // system & limit write permissions on their plugin files as a
71+ // mitigation (see docs/technical/auth-config.md).
72+ expectedChecksum , err := hex .DecodeString (config .Checksum )
73+ if err != nil {
74+ return nil , fmt .Errorf ("plugin checksum is invalid: %w" , err )
75+ }
76+ checksum , err := getPluginChecksum (config .Path )
77+ if err != nil {
78+ return nil , fmt .Errorf ("could not calculate plugin checksum: %w" , err )
79+ }
80+
81+ if ! bytes .Equal (expectedChecksum , checksum .Sum (nil )) {
82+ return nil , fmt .Errorf ("specified hash does not match plugin checksum" )
83+ }
84+
85+ // Load the plugin and find the initializer function
86+ p , err := plugin .Open (config .Path )
87+ if err != nil {
88+ return nil , fmt .Errorf ("could not load auth plugin: %w" , err )
89+ }
90+
91+ rawInit , err := p .Lookup (config .Initializer )
92+ if err != nil {
93+ return nil , fmt .Errorf ("failed to load initializer: %w" , err )
94+ }
95+
96+ initializer , ok := rawInit .(func (json.RawMessage ) (auth.AuthMiddleware , error ))
97+ if ! ok {
98+ return nil , fmt .Errorf ("initializer function has incorrect signature" )
99+ }
100+
101+ // Call the initializer
102+ return initializer (config .Parameters )
103+ default :
104+ return nil , fmt .Errorf ("unrecognized auth mode '%s'" , config .AuthMode )
105+ }
106+ }
107+
108+ type authConfig struct {
109+ AuthMode string `json:"mode"`
110+
111+ // Plugin-specific settings
112+ Path string `json:"path,omitempty"`
113+ Initializer string `json:"initializer,omitempty"`
114+ Checksum string `json:"sha256,omitempty"`
115+
116+ // Per-middleware custom config
117+ Parameters json.RawMessage `json:"parameters,omitempty"`
118+ }
119+
14120func main () {
15121 log .WithTraceLogger (context .Background (), func (ctx context.Context , logger log.TraceLogger ) {
16122 parser := argparse .NewArgParser (logger , "git-bundle-web-server [--port <port>] [--cert <filename> --key <filename>]" )
@@ -28,13 +134,35 @@ func main() {
28134 key := utils .GetFlagValue [string ](parser , "key" )
29135 tlsMinVersion := utils .GetFlagValue [uint16 ](parser , "tls-version" )
30136 clientCA := utils .GetFlagValue [string ](parser , "client-ca" )
137+ authConfig := utils .GetFlagValue [string ](parser , "auth-config" )
138+
139+ // Configure auth
140+ var err error
141+ middlewareAuthorize := authFunc (nil )
142+ if authConfig != "" {
143+ middleware , err := parseAuthConfig (authConfig )
144+ if err != nil {
145+ logger .Fatalf (ctx , "Invalid auth config: %w" , err )
146+ }
147+ if middleware == nil {
148+ // Up until this point, everything indicates that a user intends
149+ // to use - and has properly configured - custom auth. However,
150+ // despite there being no error from the initializer, the
151+ // middleware was empty. This is almost certainly incorrect, so
152+ // we exit.
153+ logger .Fatalf (ctx , "Middleware is nil, but no error was returned from initializer. " +
154+ "If no middleware is desired, remove the --auth-config option." )
155+ }
156+ middlewareAuthorize = middleware .Authorize
157+ }
31158
32159 // Configure the server
33160 bundleServer , err := NewBundleWebServer (logger ,
34161 port ,
35162 cert , key ,
36163 tlsMinVersion ,
37164 clientCA ,
165+ middlewareAuthorize ,
38166 )
39167 if err != nil {
40168 logger .Fatal (ctx , err )
0 commit comments