github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth
No package summary is available.
Package
Files: 5. Third party imports: 6. Imports from organisation: 0. Tests: 0. Benchmarks: 0.
Vars
var (
_ caddy.Provisioner = (*HTTPBasicAuth)(nil)
_ Authenticator = (*HTTPBasicAuth)(nil)
)
Types
Account
Account contains a username and password.
type Account struct {
// A user's username.
Username string `json:"username"`
// The user's hashed password, in Modular Crypt Format (with `$` prefix)
// or base64-encoded.
Password string `json:"password"`
password []byte
}
Authentication
Authentication is a middleware which provides user authentication. Rejects requests with HTTP 401 if the request is not authenticated.
After a successful authentication, the placeholder
{http.auth.user.id}
will be set to the username, and also
{http.auth.user.*}
placeholders may be set for any authentication
modules that provide user metadata.
Its API is still experimental and may be subject to change.
type Authentication struct {
// A set of authentication providers. If none are specified,
// all requests will always be unauthenticated.
ProvidersRaw caddy.ModuleMap `json:"providers,omitempty" caddy:"namespace=http.authentication.providers"`
Providers map[string]Authenticator `json:"-"`
logger *zap.Logger
}
Authenticator
Authenticator is a type which can authenticate a request. If a request was not authenticated, it returns false. An error is only returned if authenticating the request fails for a technical reason (not for bad/missing credentials).
type Authenticator interface {
Authenticate(http.ResponseWriter, *http.Request) (User, bool, error)
}
BcryptHash
BcryptHash implements the bcrypt hash.
type BcryptHash struct{}
Cache
Cache enables caching of basic auth results. This is especially helpful for secure password hashes which can be expensive to compute on every HTTP request.
type Cache struct {
mu *sync.RWMutex
g *singleflight.Group
// map of concatenated hashed password + plaintext password, to result
cache map[string]bool
}
Comparer
Comparer is a type that can securely compare a plaintext password with a hashed password in constant-time. Comparers should hash the plaintext password and then use constant-time comparison.
type Comparer interface {
// Compare returns true if the result of hashing
// plaintextPassword is hashedPassword, false
// otherwise. An error is returned only if
// there is a technical/configuration error.
Compare(hashedPassword, plaintextPassword []byte) (bool, error)
}
HTTPBasicAuth
HTTPBasicAuth facilitates HTTP basic authentication.
type HTTPBasicAuth struct {
// The algorithm with which the passwords are hashed. Default: bcrypt
HashRaw json.RawMessage `json:"hash,omitempty" caddy:"namespace=http.authentication.hashes inline_key=algorithm"`
// The list of accounts to authenticate.
AccountList []Account `json:"accounts,omitempty"`
// The name of the realm. Default: restricted
Realm string `json:"realm,omitempty"`
// If non-nil, a mapping of plaintext passwords to their
// hashes will be cached in memory (with random eviction).
// This can greatly improve the performance of traffic-heavy
// servers that use secure password hashing algorithms, with
// the downside that plaintext passwords will be stored in
// memory for a longer time (this should not be a problem
// as long as your machine is not compromised, at which point
// all bets are off, since basicauth necessitates plaintext
// passwords being received over the wire anyway). Note that
// a cache hit does not mean it is a valid password.
HashCache *Cache `json:"hash_cache,omitempty"`
Accounts map[string]Account `json:"-"`
Hash Comparer `json:"-"`
// fakePassword is used when a given user is not found,
// so that timing side-channels can be mitigated: it gives
// us something to hash and compare even if the user does
// not exist, which should have similar timing as a user
// account that does exist.
fakePassword []byte
}
Hasher
Hasher is a type that can generate a secure hash given a plaintext. Hashing modules which implement this interface can be used with the hash-password subcommand as well as benefitting from anti-timing features. A hasher also returns a fake hash which can be used for timing side-channel mitigation.
type Hasher interface {
Hash(plaintext []byte) ([]byte, error)
FakeHash() []byte
}
User
User represents an authenticated user.
type User struct {
// The ID of the authenticated user.
ID string
// Any other relevant data about this
// user. Keys should be adhere to Caddy
// conventions (snake_casing), as all
// keys will be made available as
// placeholders.
Metadata map[string]string
}
Functions
func (*HTTPBasicAuth) Provision
Provision provisions the HTTP basic auth provider.
func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
if hba.HashRaw == nil {
hba.HashRaw = json.RawMessage(`{"algorithm": "bcrypt"}`)
}
// load password hasher
hasherIface, err := ctx.LoadModule(hba, "HashRaw")
if err != nil {
return fmt.Errorf("loading password hasher module: %v", err)
}
hba.Hash = hasherIface.(Comparer)
if hba.Hash == nil {
return fmt.Errorf("hash is required")
}
// if supported, generate a fake password we can compare against if needed
if hasher, ok := hba.Hash.(Hasher); ok {
hba.fakePassword = hasher.FakeHash()
}
repl := caddy.NewReplacer()
// load account list
hba.Accounts = make(map[string]Account)
for i, acct := range hba.AccountList {
if _, ok := hba.Accounts[acct.Username]; ok {
return fmt.Errorf("account %d: username is not unique: %s", i, acct.Username)
}
acct.Username = repl.ReplaceAll(acct.Username, "")
acct.Password = repl.ReplaceAll(acct.Password, "")
if acct.Username == "" || acct.Password == "" {
return fmt.Errorf("account %d: username and password are required", i)
}
// TODO: Remove support for redundantly-encoded b64-encoded hashes
// Passwords starting with '$' are likely in Modular Crypt Format,
// so we don't need to base64 decode them. But historically, we
// required redundant base64, so we try to decode it otherwise.
if strings.HasPrefix(acct.Password, "$") {
acct.password = []byte(acct.Password)
} else {
acct.password, err = base64.StdEncoding.DecodeString(acct.Password)
if err != nil {
return fmt.Errorf("base64-decoding password: %v", err)
}
}
hba.Accounts[acct.Username] = acct
}
hba.AccountList = nil // allow GC to deallocate
if hba.HashCache != nil {
hba.HashCache.cache = make(map[string]bool)
hba.HashCache.mu = new(sync.RWMutex)
hba.HashCache.g = new(singleflight.Group)
}
return nil
}
Cognitive complexity: 24
, Cyclomatic complexity: 12
func (Authentication) ServeHTTP
func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
var user User
var authed bool
var err error
for provName, prov := range a.Providers {
user, authed, err = prov.Authenticate(w, r)
if err != nil {
if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil {
c.Write(zap.String("provider", provName), zap.Error(err))
}
continue
}
if authed {
break
}
}
if !authed {
return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated"))
}
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
repl.Set("http.auth.user.id", user.ID)
for k, v := range user.Metadata {
repl.Set("http.auth.user."+k, v)
}
return next.ServeHTTP(w, r)
}
Cognitive complexity: 14
, Cyclomatic complexity: 7
func (BcryptHash) Compare
Compare compares passwords.
func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
err := bcrypt.CompareHashAndPassword(hashed, plaintext)
if err == bcrypt.ErrMismatchedHashAndPassword {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
Cognitive complexity: 4
, Cyclomatic complexity: 3
func (BcryptHash) FakeHash
FakeHash returns a fake hash.
func (BcryptHash) FakeHash() []byte {
// hashed with the following command:
// caddy hash-password --plaintext "antitiming" --algorithm "bcrypt"
return []byte("$2a$14$X3ulqf/iGxnf1k6oMZ.RZeJUoqI9PX2PM4rS5lkIKJXduLGXGPrt6")
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (BcryptHash) Hash
Hash hashes plaintext using a random salt.
func (BcryptHash) Hash(plaintext []byte) ([]byte, error) {
return bcrypt.GenerateFromPassword(plaintext, 14)
}
Cognitive complexity: 0
, Cyclomatic complexity: 1
func (HTTPBasicAuth) Authenticate
Authenticate validates the user credentials in req and returns the user, if valid.
func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request) (User, bool, error) {
username, plaintextPasswordStr, ok := req.BasicAuth()
if !ok {
return hba.promptForCredentials(w, nil)
}
account, accountExists := hba.Accounts[username]
if !accountExists {
// don't return early if account does not exist; we want
// to try to avoid side-channels that leak existence, so
// we use a fake password to simulate realistic CPU cycles
account.password = hba.fakePassword
}
same, err := hba.correctPassword(account, []byte(plaintextPasswordStr))
if err != nil || !same || !accountExists {
return hba.promptForCredentials(w, err)
}
return User{ID: username}, true, nil
}
Cognitive complexity: 7
, Cyclomatic complexity: 6
func (HTTPBasicAuth) CaddyModule
CaddyModule returns the Caddy module information.
func (HTTPBasicAuth) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.authentication.providers.http_basic",
New: func() caddy.Module { return new(HTTPBasicAuth) },
}
}
Cognitive complexity: 2
, Cyclomatic complexity: 1
Private functions
func cmdHashPassword
cmdHashPassword (fs caddycmd.Flags) (int, error)
References: bufio.NewReader, bytes.Equal, fmt.Errorf, fmt.Fprint, fmt.Fprintln, fmt.Println, os.Exit, os.Interrupt, os.Signal, os.Stderr, os.Stdin, signal.Notify, signal.Stop, term.GetState, term.IsTerminal, term.ReadPassword, term.Restore.
func init
init ()
func parseCaddyfile
parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
basic_auth [<matcher>] [<hash_algorithm> [<realm>]] {
<username> <hashed_password>
...
}
If no hash algorithm is supplied, bcrypt will be assumed.
parseCaddyfile (h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
References: caddyconfig.JSON, caddyconfig.JSONModuleObject.
func makeRoom
makeRoom deletes about 1/10 of the items in the cache in order to keep its size under control. It must not be called without a lock on c.mu.
makeRoom ()
References: weakrand.Intn.
func correctPassword
correctPassword (account Account, plaintextPassword []byte) (bool, error)
References: hex.EncodeToString.
func promptForCredentials
promptForCredentials (w http.ResponseWriter, err error) (User, bool, error)
References: fmt.Sprintf.