Skip to content
Snippets Groups Projects
Commit fce7b71c authored by ale's avatar ale
Browse files

Propagate backend errors upstream

This allows Dovecot to fail temporarily if the key lookup backend is
unavailable or has issues talking to LDAP.
parent 1d58d5ea
No related branches found
No related tags found
No related merge requests found
...@@ -3,8 +3,8 @@ package backend ...@@ -3,8 +3,8 @@ package backend
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"log"
"strings" "strings"
ldaputil "git.autistici.org/ai3/go-common/ldap" ldaputil "git.autistici.org/ai3/go-common/ldap"
...@@ -133,11 +133,10 @@ func NewLDAPBackend(config *LDAPConfig) (*ldapBackend, error) { ...@@ -133,11 +133,10 @@ func NewLDAPBackend(config *LDAPConfig) (*ldapBackend, error) {
}, nil }, nil
} }
func (b *ldapBackend) GetPrivateKeys(ctx context.Context, username string) [][]byte { func (b *ldapBackend) GetPrivateKeys(ctx context.Context, username string) ([][]byte, error) {
result, err := b.pool.Search(ctx, b.config.Query.searchRequest(username, b.config.Query.PrivateKeyAttr)) result, err := b.pool.Search(ctx, b.config.Query.searchRequest(username, b.config.Query.PrivateKeyAttr))
if err != nil { if err != nil {
log.Printf("LDAP error: %v", err) return nil, err
return nil
} }
var out [][]byte var out [][]byte
...@@ -146,26 +145,24 @@ func (b *ldapBackend) GetPrivateKeys(ctx context.Context, username string) [][]b ...@@ -146,26 +145,24 @@ func (b *ldapBackend) GetPrivateKeys(ctx context.Context, username string) [][]b
out = append(out, []byte(val)) out = append(out, []byte(val))
} }
} }
return out return out, nil
} }
func (b *ldapBackend) GetPublicKey(ctx context.Context, username string) []byte { func (b *ldapBackend) GetPublicKey(ctx context.Context, username string) ([]byte, error) {
result, err := b.pool.Search(ctx, b.config.Query.searchRequest(username, b.config.Query.PublicKeyAttr)) result, err := b.pool.Search(ctx, b.config.Query.searchRequest(username, b.config.Query.PublicKeyAttr))
if err != nil { if err != nil {
log.Printf("LDAP error: %v", err) return nil, err
return nil
} }
if len(result.Entries) == 0 { if len(result.Entries) == 0 {
return nil return nil, nil
} }
if len(result.Entries) > 1 { if len(result.Entries) > 1 {
log.Printf("public key query for %s returned too many results (%d)", username, len(result.Entries)) return nil, fmt.Errorf("public key query for %s returned too many results (%d)", username, len(result.Entries))
return nil
} }
s := result.Entries[0].GetAttributeValue(b.config.Query.PublicKeyAttr) s := result.Entries[0].GetAttributeValue(b.config.Query.PublicKeyAttr)
if s == "" { if s == "" {
return nil return nil, nil
} }
return []byte(s) return []byte(s), nil
} }
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"log"
"git.autistici.org/ai3/go-common/unix" "git.autistici.org/ai3/go-common/unix"
) )
...@@ -17,7 +18,10 @@ var ( ...@@ -17,7 +18,10 @@ var (
// DictDatabase is an interface to a key/value store by way of the Lookup // DictDatabase is an interface to a key/value store by way of the Lookup
// method. // method.
type DictDatabase interface { type DictDatabase interface {
Lookup(context.Context, string) (interface{}, bool) // Lookup a key. The result boolean is the presence flag, to
// avoid having to check the object for nullness. Errors
// result in failure being propagated upstream to Dovecot.
Lookup(context.Context, string) (interface{}, bool, error)
} }
// DictProxyServer exposes a Database using the Dovecot dict proxy // DictProxyServer exposes a Database using the Dovecot dict proxy
...@@ -56,7 +60,11 @@ func (p *DictProxyServer) handleHello(ctx context.Context, lw unix.LineResponseW ...@@ -56,7 +60,11 @@ func (p *DictProxyServer) handleHello(ctx context.Context, lw unix.LineResponseW
} }
func (p *DictProxyServer) handleLookup(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error { func (p *DictProxyServer) handleLookup(ctx context.Context, lw unix.LineResponseWriter, arg []byte) error {
obj, ok := p.db.Lookup(ctx, string(arg)) obj, ok, err := p.db.Lookup(ctx, string(arg))
if err != nil {
log.Printf("error: %v", err)
return lw.WriteLine(failResponse)
}
if !ok { if !ok {
return lw.WriteLine(noMatchResponse) return lw.WriteLine(noMatchResponse)
} }
......
...@@ -24,8 +24,8 @@ type Config struct { ...@@ -24,8 +24,8 @@ type Config struct {
// Database represents the interface to the underlying backend for // Database represents the interface to the underlying backend for
// encrypted user keys. // encrypted user keys.
type Database interface { type Database interface {
GetPublicKey(context.Context, string) []byte GetPublicKey(context.Context, string) ([]byte, error)
GetPrivateKeys(context.Context, string) [][]byte GetPrivateKeys(context.Context, string) ([][]byte, error)
} }
func (c *Config) check() error { func (c *Config) check() error {
...@@ -82,7 +82,7 @@ func NewKeyLookupProxy(config *Config) (*KeyLookupProxy, error) { ...@@ -82,7 +82,7 @@ func NewKeyLookupProxy(config *Config) (*KeyLookupProxy, error) {
// //
// We can be sent a userdb lookup, or a passdb lookup, and we can tell // We can be sent a userdb lookup, or a passdb lookup, and we can tell
// them apart with the key prefix (passdb/ or userdb/). // them apart with the key prefix (passdb/ or userdb/).
func (s *KeyLookupProxy) Lookup(ctx context.Context, key string) (interface{}, bool) { func (s *KeyLookupProxy) Lookup(ctx context.Context, key string) (interface{}, bool, error) {
switch { switch {
case strings.HasPrefix(key, "passdb/"): case strings.HasPrefix(key, "passdb/"):
kparts := strings.SplitN(key[7:], passwordSep, 2) kparts := strings.SplitN(key[7:], passwordSep, 2)
...@@ -95,39 +95,45 @@ func (s *KeyLookupProxy) Lookup(ctx context.Context, key string) (interface{}, b ...@@ -95,39 +95,45 @@ func (s *KeyLookupProxy) Lookup(ctx context.Context, key string) (interface{}, b
} }
} }
func (s *KeyLookupProxy) lookupUserdb(ctx context.Context, username string) (interface{}, bool) { func (s *KeyLookupProxy) lookupUserdb(ctx context.Context, username string) (interface{}, bool, error) {
pub := s.db.GetPublicKey(ctx, username) pub, err := s.db.GetPublicKey(ctx, username)
if err != nil {
return nil, false, err
}
if pub == nil { if pub == nil {
log.Printf("failed userdb lookup for %s", username) log.Printf("userdb lookup for %s (no keys)", username)
return nil, false return nil, false, nil
} }
log.Printf("userdb lookup for %s", username) log.Printf("userdb lookup for %s", username)
return &userdbResponse{PublicKey: b64encode(pub)}, true return &userdbResponse{PublicKey: b64encode(pub)}, true, nil
} }
func (s *KeyLookupProxy) lookupPassdb(ctx context.Context, username, password string) (interface{}, bool) { func (s *KeyLookupProxy) lookupPassdb(ctx context.Context, username, password string) (interface{}, bool, error) {
// If the password is a SSO token, try to fetch the // If the password is a SSO token, try to fetch the
// unencrypted key from the keystore daemon. // unencrypted key from the keystore daemon.
priv, err := s.keystore.Get(ctx, s.config.Shard, username, password) priv, err := s.keystore.Get(ctx, s.config.Shard, username, password)
if err == nil { if err == nil {
log.Printf("passdb lookup for %s (from keystore)", username) log.Printf("passdb lookup for %s (from keystore)", username)
return &passdbResponse{PrivateKey: b64encode(priv)}, true return &passdbResponse{PrivateKey: b64encode(priv)}, true, nil
} }
// Otherwise, fetch encrypted keys from the db and attempt to // Otherwise, fetch encrypted keys from the db and attempt to
// decrypt them. // decrypt them.
encKeys := s.db.GetPrivateKeys(ctx, username) encKeys, err := s.db.GetPrivateKeys(ctx, username)
if err != nil {
return nil, false, err
}
if len(encKeys) == 0 { if len(encKeys) == 0 {
log.Printf("failed passdb lookup for %s (no keys)", username) log.Printf("failed passdb lookup for %s (no keys)", username)
return nil, false return nil, false, nil
} }
priv, err = userenckey.Decrypt(encKeys, []byte(password)) priv, err = userenckey.Decrypt(encKeys, []byte(password))
if err != nil { if err != nil {
log.Printf("failed passdb lookup for %s (could not decrypt key)", username) log.Printf("failed passdb lookup for %s (could not decrypt key)", username)
return nil, false return nil, false, err
} }
log.Printf("passdb lookup for %s (decrypted)", username) log.Printf("passdb lookup for %s (decrypted)", username)
return &passdbResponse{PrivateKey: b64encode(priv)}, true return &passdbResponse{PrivateKey: b64encode(priv)}, true, nil
} }
func b64encode(b []byte) string { func b64encode(b []byte) string {
......
...@@ -23,7 +23,7 @@ var ( ...@@ -23,7 +23,7 @@ var (
// Database represents the interface to the underlying backend for // Database represents the interface to the underlying backend for
// encrypted user keys. // encrypted user keys.
type Database interface { type Database interface {
GetPrivateKeys(context.Context, string) [][]byte GetPrivateKeys(context.Context, string) ([][]byte, error)
} }
type userKey struct { type userKey struct {
...@@ -133,7 +133,10 @@ func (s *KeyStore) Open(ctx context.Context, username, password string, ttlSecon ...@@ -133,7 +133,10 @@ func (s *KeyStore) Open(ctx context.Context, username, password string, ttlSecon
return ErrInvalidTTL return ErrInvalidTTL
} }
encKeys := s.db.GetPrivateKeys(ctx, username) encKeys, err := s.db.GetPrivateKeys(ctx, username)
if err != nil {
return err
}
if len(encKeys) == 0 { if len(encKeys) == 0 {
return ErrNoKeys return ErrNoKeys
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment