Go API Documentation

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

Uses: base64.StdEncoding, fmt.Errorf, json.RawMessage, singleflight.Group, strings.HasPrefix, sync.RWMutex.

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

Uses: caddyhttp.Error, fmt.Errorf, http.StatusUnauthorized, zap.Error, zap.String, zapcore.ErrorLevel.

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

Uses: bcrypt.CompareHashAndPassword, bcrypt.ErrMismatchedHashAndPassword.

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

Uses: bcrypt.GenerateFromPassword.

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.