Documentation
¶
Overview ¶
Package directory answers three questions about the people a server serves: who is here, what proves them, and who is in which group.
The answers come from wherever a site already keeps them — a database, an LDAP server, a file, or the program's own configuration — and every source answers the same three questions, so the code that USES an identity never learns where it came from.
What proves somebody is not one thing ¶
This is the whole reason the package exists, and it is a fact about protocols rather than a design choice:
NTLMv2 (SMB, Windows file sharing) is a CHALLENGE-RESPONSE. The client never sends the password, so the server must compute MD4(UTF16LE(password)) itself. An LDAP bind cannot answer it. Neither can a bcrypt. What answers it is the password, or that MD4 — the "NT hash", which is exactly what Samba's sambaNTPassword attribute holds. HTTP Basic, and anything else that hands the server the password, can be answered by ANY verifier: a comparison, a hash, or a bind against a directory that holds the secret and will not give it up. SSH authenticates with a public key, or a certificate from an authority. No password is involved at all, and a directory publishes the keys — OpenSSH's convention is the sshPublicKey attribute.
So an Identity carries what its source could give, each protocol uses what it can, and Identity.Can says which is which. A server can then tell a person "you can use WebDAV and SFTP, and not SMB, because this directory holds a bcrypt" — before they meet a refusal at a mount.
Reading is done once ¶
A source is read when the server starts, and a change to it is picked up by a restart. That is a deliberate limit rather than an oversight: the alternative is asking the directory on every connection, which is a different design with different failure modes (a server that stops authenticating when the database blinks), and this package says which one it is rather than implying.
The exception is a PASSWORD CHECK: Identity.Verify may reach the source every time, because a bind is the only way an LDAP directory will answer "is this the right password" and it cannot be cached without holding the secret this package went out of its way not to hold.
Index ¶
- Constants
- Variables
- func Expand(names []string, src Source) ([]string, error)
- func IsGroup(name string) bool
- func NTHashOf(password string) []byte
- func ParseNTHash(s string) ([]byte, error)
- func ParseTOTPSecret(s string) ([]byte, error)
- type Credential
- type GroupLister
- type Identity
- func (i *Identity) Can(c Credential) bool
- func (i *Identity) Groups() []string
- func (i *Identity) KerberosKey(derive func(password string) ([]byte, error)) ([]byte, error)
- func (i *Identity) Keys() []string
- func (i *Identity) NTKey() ([]byte, error)
- func (i *Identity) Name() string
- func (i *Identity) TOTPSecret() []byte
- func (i *Identity) Verify(password string) error
- func (i *Identity) Where() string
- type Option
- type Set
- type Source
- type Static
Constants ¶
const GroupPrefix = "@"
GroupPrefix is how a group is written where a person could be: @staff. It is Samba's spelling (`valid users = @staff`) and what anybody administering a file server types without being told.
Variables ¶
var ErrNoCredential = errors.New("directory: this identity has no such credential")
ErrNoCredential says this identity cannot answer that question at all, which is a different thing from answering it wrongly: one is a configuration to fix, the other is somebody typing the wrong password.
var ErrNoSuchGroup = errors.New("directory: no such group")
ErrNoSuchGroup is what a source returns for a name it does not have.
var ErrWrongPassword = errors.New("directory: wrong password")
ErrWrongPassword is what a failed check returns.
Functions ¶
func Expand ¶
Expand turns a list of names — people and groups — into the people in it, keeping the order and dropping repeats.
A group with nobody in it is refused rather than expanded to nothing: a configuration that grants nothing to nobody reads exactly like one that works.
func NTHashOf ¶
NTHashOf is MD4(UTF16LE(password)), the value Samba stores as sambaNTPassword and Windows calls the NT hash.
MD4 is broken as a hash function and is used here anyway, because NTLMv2 is DEFINED over it: this is not a choice about security, it is the arithmetic the protocol specifies, and a server that used anything else would refuse every correct password.
func ParseNTHash ¶
ParseNTHash reads the hex a directory publishes.
A hash that is not 16 bytes is refused rather than padded or truncated: a mangled one would fail every login with "wrong password", which is the least useful thing a server could say.
func ParseTOTPSecret ¶ added in v0.4.0
ParseTOTPSecret reads the base32 a directory stores a one-time-code secret as: upper or lower case, spaces and padding optional, which is how people copy them out of an authenticator.
github.com/go-authn/totp reads the same shape and neither package imports the other: this one is about who somebody IS, that one is about one way of checking, and a dependency either way would make every consumer of one carry the other. Eight lines of encoding/base32 is the cheaper of the two prices, and it is written down here so that the next reader knows it was a choice.
Types ¶
type Credential ¶
type Credential int
A Credential is one way of proving somebody. They are named so that a server can SAY which it has and which a protocol needs, rather than discovering the mismatch when somebody fails to log in.
const ( // Password is the password itself, which this process then holds. It // answers everything, and is the only thing that does. Password Credential = iota // NTHash is MD4(UTF16LE(password)) — Samba's sambaNTPassword. It answers // NTLMv2 and nothing else: it cannot be compared against a password a // client sent, because it IS the thing derived from it. NTHash // Verifier answers "is this the right password" without this process // knowing it: a hash comparison, or a bind against the directory that // holds it. Verifier // PublicKeys are SSH public keys, in authorized_keys spelling. PublicKeys // TOTPSecret is the shared secret behind the six digits on a phone (RFC // 6238). It is a SECOND factor and never a first: it proves the person // holds the thing it was enrolled into, and says nothing about who they // are. A server asking for two factors needs to know who has one — which // is the same question Can() answers for every other credential. TOTPSecret )
func (Credential) String ¶
func (c Credential) String() string
type GroupLister ¶ added in v0.3.0
A GroupLister is a source that can say WHICH groups it has, rather than only answering about one by name.
It is a separate, optional interface because not every source can. Members asks a question with the name in hand -- an LDAP filter, a WHERE clause -- and a source that answers those need not be able to enumerate: a query that lists people says nothing about groups at all, and a directory may permit one and refuse the other.
A server that PUBLISHES groups needs this; one that only checks membership does not, which is why adding it did not change the Source interface.
type Identity ¶
type Identity struct {
// contains filtered or unexported fields
}
An Identity is one person, and what a source could give to prove them.
The zero value is nobody. Build one with NewIdentity or take one from a Source.
func NewIdentity ¶
NewIdentity names somebody. Credentials are added with the With… options, and a person with none can be named but proved by nothing — which is a legitimate state (a directory listing everybody, with the secrets somewhere else) and one Identity.Can reports honestly.
func (*Identity) Can ¶
func (i *Identity) Can(c Credential) bool
Can reports whether this identity carries a given credential — which is what lets a server say "you can use WebDAV and not SMB" before somebody finds out the hard way.
func (*Identity) KerberosKey ¶ added in v0.5.0
KerberosKey derives this identity's Kerberos long-term key.
The derivation is the CALLER's, and that is the whole design. A Kerberos key is string2key(password, salt, enctype) — arithmetic this package has no business carrying, since it would drag a Kerberos library into everything that merely wants to list users. Passing the function in means the password never leaves here and the enctype table stays where enctypes are understood.
It answers only for an identity that carries the PASSWORD. A Verifier — a bind against somebody else's directory, or a hash comparison — cannot serve Kerberos at all: a KDC has to DECRYPT the client's pre-authentication with this key, and "is this the right password" does not produce one. That is a property of Kerberos, not a limitation here, and a server should say so at configuration time rather than at the first kinit.
func (*Identity) NTKey ¶
NTKey is the key NTLMv2 is computed with: MD4(UTF16LE(password)).
It comes from the hash when the source published one, and is derived from the password when the source gave that instead. A source with neither cannot serve SMB, and this says so rather than returning something that will fail every login with "wrong password".
func (*Identity) TOTPSecret ¶ added in v0.4.0
TOTPSecret is the shared secret behind this person's one-time codes, or nil.
⛔ Like Identity.NTKey, this hands out a credential: whoever holds it produces every future code. It is here because a server that must CHECK a code has to have it, and a copy is returned so that the caller cannot change what a directory said.
func (*Identity) Verify ¶
Verify answers "is this the right password".
It prefers the verifier a source gave — that is the one that may reach an LDAP server, and the one that exists precisely so this process need not hold the secret. Falling back to a held password is a constant-time comparison: the difference between "wrong at byte 1" and "wrong at byte 12" is measurable over a network.
type Option ¶
type Option func(*Identity)
An Option adds something to an identity.
func WithGroups ¶
WithGroups says which groups this person is in, when the source knows without being asked.
func WithNTHash ¶
WithNTHash gives MD4(UTF16LE(password)) — 16 bytes, as a directory publishes it. Anybody holding this can authenticate as that person exactly as if they held the password: it is not a password hash in the sense a login form means, and storing it does not make a leak less bad.
func WithPassword ¶
WithPassword gives the password itself, which answers every protocol.
func WithPublicKeys ¶
WithPublicKeys gives SSH public keys, each in authorized_keys spelling.
func WithTOTPSecret ¶ added in v0.4.0
WithTOTPSecret gives the shared secret behind a one-time code (RFC 6238), as the raw bytes — github.com/go-authn/totp's ParseSecret reads the base32 spelling a directory usually stores.
⛔ It is the credential, not a hash of one: anybody holding it produces every future code, and it does not expire. A source that publishes it has published the second factor, exactly as with an NT hash.
func WithVerifier ¶
WithVerifier gives a way to check a password without holding it: a hash comparison, or a bind. The error it returns is reported to the SERVER, never to the client — a client is told only that authentication failed, which is the right amount to tell somebody who has not proved who they are.
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
A Set is several sources read as one.
A site with its people in LDAP and one service account written down locally should not have to put the service account in LDAP. So the sources are asked in the order they were given and the FIRST one that knows a name owns it — which is the rule a person can hold in their head, and the one that lets a local file override a directory rather than the other way round.
func (*Set) GroupNames ¶ added in v0.3.0
GroupNames is every group the sources can name, sorted and without repeats.
Sources that cannot list groups are skipped rather than refused, so this can legitimately return FEWER groups than Set.Members would answer for: an LDAP directory that will not enumerate still answers about a group somebody names. A caller publishing a list should say where it came from, and a caller checking membership should keep asking Members.
func (*Set) Identities ¶
type Source ¶
type Source interface {
// Identities is everybody this source knows, with whatever credentials it
// has for them.
Identities() ([]*Identity, error)
// Members expands one group. A group the source has never heard of is an
// ERROR, not an empty list: an empty list quietly grants nothing to
// nobody, which reads exactly like a working configuration.
Members(group string) ([]string, error)
// Describe says what this is, for a server to print: "a postgres
// database", "ldaps://ldap.example.org", "the configuration file".
Describe() string
}
A Source is somewhere people are kept.
The three questions are the whole interface, and they are the three a file server actually asks: who is here, who is in this group, and where did that come from.
type Static ¶
Static is a source held in memory: what a program's own configuration file becomes once it is read, and what a test uses.
func (*Static) GroupNames ¶ added in v0.3.0
GroupNames is the groups this list holds, which it knows exactly.
func (*Static) Identities ¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package hcldir is the `users` block: a directory named in a configuration file, opened.
|
Package hcldir is the `users` block: a directory named in a configuration file, opened. |
|
Package ldapdir reads people and groups from an LDAP directory.
|
Package ldapdir reads people and groups from an LDAP directory. |
|
Package ldaptest is an LDAP directory to test against.
|
Package ldaptest is an LDAP directory to test against. |
|
Package sqldir reads people and groups from a database.
|
Package sqldir reads people and groups from a database. |