1// Copyright 2020 The Prometheus Authors 2// This code is partly borrowed from Caddy: 3// Copyright 2015 Matthew Holt and The Caddy Authors 4// Licensed under the Apache License, Version 2.0 (the "License"); 5// you may not use this file except in compliance with the License. 6// You may obtain a copy of the License at 7// 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15 16package web 17 18import ( 19 "encoding/hex" 20 "net/http" 21 "sync" 22 23 "github.com/go-kit/log" 24 "golang.org/x/crypto/bcrypt" 25) 26 27func validateUsers(configPath string) error { 28 c, err := getConfig(configPath) 29 if err != nil { 30 return err 31 } 32 33 for _, p := range c.Users { 34 _, err = bcrypt.Cost([]byte(p)) 35 if err != nil { 36 return err 37 } 38 } 39 40 return nil 41} 42 43type userAuthRoundtrip struct { 44 tlsConfigPath string 45 handler http.Handler 46 logger log.Logger 47 cache *cache 48 // bcryptMtx is there to ensure that bcrypt.CompareHashAndPassword is run 49 // only once in parallel as this is CPU intensive. 50 bcryptMtx sync.Mutex 51} 52 53func (u *userAuthRoundtrip) ServeHTTP(w http.ResponseWriter, r *http.Request) { 54 c, err := getConfig(u.tlsConfigPath) 55 if err != nil { 56 u.logger.Log("msg", "Unable to parse configuration", "err", err) 57 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 58 return 59 } 60 61 if len(c.Users) == 0 { 62 u.handler.ServeHTTP(w, r) 63 return 64 } 65 66 user, pass, auth := r.BasicAuth() 67 if auth { 68 hashedPassword, validUser := c.Users[user] 69 70 if !validUser { 71 // The user is not found. Use a fixed password hash to 72 // prevent user enumeration by timing requests. 73 // This is a bcrypt-hashed version of "fakepassword". 74 hashedPassword = "$2y$10$QOauhQNbBCuQDKes6eFzPeMqBSjb7Mr5DUmpZ/VcEd00UAV/LDeSi" 75 } 76 77 cacheKey := hex.EncodeToString(append(append([]byte(user), []byte(hashedPassword)...), []byte(pass)...)) 78 authOk, ok := u.cache.get(cacheKey) 79 80 if !ok { 81 // This user, hashedPassword, password is not cached. 82 u.bcryptMtx.Lock() 83 err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(pass)) 84 u.bcryptMtx.Unlock() 85 86 authOk = err == nil 87 u.cache.set(cacheKey, authOk) 88 } 89 90 if authOk && validUser { 91 u.handler.ServeHTTP(w, r) 92 return 93 } 94 } 95 96 w.Header().Set("WWW-Authenticate", "Basic") 97 http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 98} 99