github.com/anuvu/zot@v1.3.4/pkg/api/authn.go (about) 1 package api 2 3 import ( 4 "bufio" 5 "crypto/x509" 6 "encoding/base64" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/anuvu/zot/errors" 16 "github.com/anuvu/zot/pkg/api/config" 17 "github.com/chartmuseum/auth" 18 "github.com/gorilla/mux" 19 "golang.org/x/crypto/bcrypt" 20 ) 21 22 const ( 23 bearerAuthDefaultAccessEntryType = "repository" 24 ) 25 26 func AuthHandler(c *Controller) mux.MiddlewareFunc { 27 if isBearerAuthEnabled(c.Config) { 28 return bearerAuthHandler(c) 29 } 30 31 return basicAuthHandler(c) 32 } 33 34 func bearerAuthHandler(c *Controller) mux.MiddlewareFunc { 35 authorizer, err := auth.NewAuthorizer(&auth.AuthorizerOptions{ 36 Realm: c.Config.HTTP.Auth.Bearer.Realm, 37 Service: c.Config.HTTP.Auth.Bearer.Service, 38 PublicKeyPath: c.Config.HTTP.Auth.Bearer.Cert, 39 AccessEntryType: bearerAuthDefaultAccessEntryType, 40 EmptyDefaultNamespace: true, 41 }) 42 if err != nil { 43 c.Log.Panic().Err(err).Msg("error creating bearer authorizer") 44 } 45 46 return func(next http.Handler) http.Handler { 47 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 48 vars := mux.Vars(r) 49 name := vars["name"] 50 header := r.Header.Get("Authorization") 51 action := auth.PullAction 52 if m := r.Method; m != http.MethodGet && m != http.MethodHead { 53 action = auth.PushAction 54 } 55 permissions, err := authorizer.Authorize(header, action, name) 56 if err != nil { 57 c.Log.Error().Err(err).Msg("issue parsing Authorization header") 58 w.Header().Set("Content-Type", "application/json") 59 WriteJSON(w, http.StatusInternalServerError, NewErrorList(NewError(UNSUPPORTED))) 60 return 61 } 62 if !permissions.Allowed { 63 authFail(w, permissions.WWWAuthenticateHeader, 0) 64 return 65 } 66 next.ServeHTTP(w, r) 67 }) 68 } 69 } 70 71 // nolint:gocyclo // we use closure making this a complex subroutine 72 func basicAuthHandler(c *Controller) mux.MiddlewareFunc { 73 realm := c.Config.HTTP.Realm 74 if realm == "" { 75 realm = "Authorization Required" 76 } 77 78 realm = "Basic realm=" + strconv.Quote(realm) 79 80 // no password based authN, if neither LDAP nor HTTP BASIC is enabled 81 if c.Config.HTTP.Auth == nil || (c.Config.HTTP.Auth.HTPasswd.Path == "" && c.Config.HTTP.Auth.LDAP == nil) { 82 return func(next http.Handler) http.Handler { 83 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 84 if c.Config.HTTP.AllowReadAccess && 85 c.Config.HTTP.TLS.CACert != "" && 86 r.TLS.VerifiedChains == nil && 87 r.Method != http.MethodGet && r.Method != http.MethodHead { 88 authFail(w, realm, 5) 89 return 90 } 91 92 if (r.Method != http.MethodGet && r.Method != http.MethodHead) && c.Config.HTTP.ReadOnly { 93 // Reject modification requests in read-only mode 94 w.WriteHeader(http.StatusMethodNotAllowed) 95 return 96 } 97 // Process request 98 next.ServeHTTP(w, r) 99 }) 100 } 101 } 102 103 credMap := make(map[string]string) 104 105 delay := c.Config.HTTP.Auth.FailDelay 106 107 var ldapClient *LDAPClient 108 109 if c.Config.HTTP.Auth != nil { 110 if c.Config.HTTP.Auth.LDAP != nil { 111 l := c.Config.HTTP.Auth.LDAP 112 ldapClient = &LDAPClient{ 113 Host: l.Address, 114 Port: l.Port, 115 UseSSL: !l.Insecure, 116 SkipTLS: !l.StartTLS, 117 Base: l.BaseDN, 118 BindDN: l.BindDN, 119 BindPassword: l.BindPassword, 120 UserFilter: fmt.Sprintf("(%s=%%s)", l.UserAttribute), 121 InsecureSkipVerify: l.SkipVerify, 122 ServerName: l.Address, 123 Log: c.Log, 124 SubtreeSearch: l.SubtreeSearch, 125 } 126 127 if c.Config.HTTP.Auth.LDAP.CACert != "" { 128 caCert, err := ioutil.ReadFile(c.Config.HTTP.Auth.LDAP.CACert) 129 130 if err != nil { 131 panic(err) 132 } 133 134 caCertPool := x509.NewCertPool() 135 136 if !caCertPool.AppendCertsFromPEM(caCert) { 137 panic(errors.ErrBadCACert) 138 } 139 140 ldapClient.ClientCAs = caCertPool 141 } else { 142 // default to system cert pool 143 caCertPool, err := x509.SystemCertPool() 144 145 if err != nil { 146 panic(errors.ErrBadCACert) 147 } 148 149 ldapClient.ClientCAs = caCertPool 150 } 151 } 152 153 if c.Config.HTTP.Auth.HTPasswd.Path != "" { 154 f, err := os.Open(c.Config.HTTP.Auth.HTPasswd.Path) 155 156 if err != nil { 157 panic(err) 158 } 159 defer f.Close() 160 161 scanner := bufio.NewScanner(f) 162 163 for scanner.Scan() { 164 line := scanner.Text() 165 if strings.Contains(line, ":") { 166 tokens := strings.Split(scanner.Text(), ":") 167 credMap[tokens[0]] = tokens[1] 168 } 169 } 170 } 171 } 172 173 return func(next http.Handler) http.Handler { 174 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 175 if (r.Method == http.MethodGet || r.Method == http.MethodHead) && c.Config.HTTP.AllowReadAccess { 176 // Process request 177 next.ServeHTTP(w, r) 178 return 179 } 180 181 if (r.Method != http.MethodGet && r.Method != http.MethodHead) && c.Config.HTTP.ReadOnly { 182 // Reject modification requests in read-only mode 183 w.WriteHeader(http.StatusMethodNotAllowed) 184 return 185 } 186 187 basicAuth := r.Header.Get("Authorization") 188 if basicAuth == "" { 189 authFail(w, realm, delay) 190 return 191 } 192 193 s := strings.SplitN(basicAuth, " ", 2) 194 195 if len(s) != 2 || strings.ToLower(s[0]) != "basic" { 196 authFail(w, realm, delay) 197 return 198 } 199 200 b, err := base64.StdEncoding.DecodeString(s[1]) 201 if err != nil { 202 authFail(w, realm, delay) 203 return 204 } 205 206 pair := strings.SplitN(string(b), ":", 2) 207 // nolint:gomnd 208 if len(pair) != 2 { 209 authFail(w, realm, delay) 210 return 211 } 212 213 username := pair[0] 214 passphrase := pair[1] 215 216 // first, HTTPPassword authN (which is local) 217 passphraseHash, ok := credMap[username] 218 if ok { 219 if err := bcrypt.CompareHashAndPassword([]byte(passphraseHash), []byte(passphrase)); err == nil { 220 // Process request 221 next.ServeHTTP(w, r) 222 return 223 } 224 } 225 226 // next, LDAP if configured (network-based which can lose connectivity) 227 if c.Config.HTTP.Auth != nil && c.Config.HTTP.Auth.LDAP != nil { 228 ok, _, err := ldapClient.Authenticate(username, passphrase) 229 if ok && err == nil { 230 // Process request 231 next.ServeHTTP(w, r) 232 return 233 } 234 } 235 236 authFail(w, realm, delay) 237 }) 238 } 239 } 240 241 func isAuthnEnabled(config *config.Config) bool { 242 if config.HTTP.Auth != nil && 243 (config.HTTP.Auth.HTPasswd.Path != "" || config.HTTP.Auth.LDAP != nil) { 244 return true 245 } 246 247 return false 248 } 249 250 func isBearerAuthEnabled(config *config.Config) bool { 251 if config.HTTP.Auth != nil && 252 config.HTTP.Auth.Bearer != nil && 253 config.HTTP.Auth.Bearer.Cert != "" && 254 config.HTTP.Auth.Bearer.Realm != "" && 255 config.HTTP.Auth.Bearer.Service != "" { 256 return true 257 } 258 259 return false 260 } 261 262 func authFail(w http.ResponseWriter, realm string, delay int) { 263 time.Sleep(time.Duration(delay) * time.Second) 264 w.Header().Set("WWW-Authenticate", realm) 265 w.Header().Set("Content-Type", "application/json") 266 WriteJSON(w, http.StatusUnauthorized, NewErrorList(NewError(UNAUTHORIZED))) 267 }