From fce7b71cff0bf509a33b74633fa30c71dbe0105d Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Sat, 13 Jan 2018 13:49:23 +0000 Subject: [PATCH] Propagate backend errors upstream This allows Dovecot to fail temporarily if the key lookup backend is unavailable or has issues talking to LDAP. --- backend/ldap.go | 23 ++++++++++------------- dovecot/dict.go | 12 ++++++++++-- dovecot/keyproxy.go | 34 ++++++++++++++++++++-------------- server/keystore.go | 7 +++++-- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/backend/ldap.go b/backend/ldap.go index f4f5e4d6..a27b3a8a 100644 --- a/backend/ldap.go +++ b/backend/ldap.go @@ -3,8 +3,8 @@ package backend import ( "context" "errors" + "fmt" "io/ioutil" - "log" "strings" ldaputil "git.autistici.org/ai3/go-common/ldap" @@ -133,11 +133,10 @@ func NewLDAPBackend(config *LDAPConfig) (*ldapBackend, error) { }, 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)) if err != nil { - log.Printf("LDAP error: %v", err) - return nil + return nil, err } var out [][]byte @@ -146,26 +145,24 @@ func (b *ldapBackend) GetPrivateKeys(ctx context.Context, username string) [][]b 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)) if err != nil { - log.Printf("LDAP error: %v", err) - return nil + return nil, err } if len(result.Entries) == 0 { - return nil + return nil, nil } if len(result.Entries) > 1 { - log.Printf("public key query for %s returned too many results (%d)", username, len(result.Entries)) - return nil + return nil, fmt.Errorf("public key query for %s returned too many results (%d)", username, len(result.Entries)) } s := result.Entries[0].GetAttributeValue(b.config.Query.PublicKeyAttr) if s == "" { - return nil + return nil, nil } - return []byte(s) + return []byte(s), nil } diff --git a/dovecot/dict.go b/dovecot/dict.go index 8278e3c4..fe605380 100644 --- a/dovecot/dict.go +++ b/dovecot/dict.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "errors" + "log" "git.autistici.org/ai3/go-common/unix" ) @@ -17,7 +18,10 @@ var ( // DictDatabase is an interface to a key/value store by way of the Lookup // method. 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 @@ -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 { - 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 { return lw.WriteLine(noMatchResponse) } diff --git a/dovecot/keyproxy.go b/dovecot/keyproxy.go index 628cad9c..f1248dcc 100644 --- a/dovecot/keyproxy.go +++ b/dovecot/keyproxy.go @@ -24,8 +24,8 @@ type Config struct { // Database represents the interface to the underlying backend for // encrypted user keys. type Database interface { - GetPublicKey(context.Context, string) []byte - GetPrivateKeys(context.Context, string) [][]byte + GetPublicKey(context.Context, string) ([]byte, error) + GetPrivateKeys(context.Context, string) ([][]byte, error) } func (c *Config) check() 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 // 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 { case strings.HasPrefix(key, "passdb/"): kparts := strings.SplitN(key[7:], passwordSep, 2) @@ -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) { - pub := s.db.GetPublicKey(ctx, username) +func (s *KeyLookupProxy) lookupUserdb(ctx context.Context, username string) (interface{}, bool, error) { + pub, err := s.db.GetPublicKey(ctx, username) + if err != nil { + return nil, false, err + } if pub == nil { - log.Printf("failed userdb lookup for %s", username) - return nil, false + log.Printf("userdb lookup for %s (no keys)", username) + return nil, false, nil } 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 // unencrypted key from the keystore daemon. priv, err := s.keystore.Get(ctx, s.config.Shard, username, password) if err == nil { 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 // 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 { log.Printf("failed passdb lookup for %s (no keys)", username) - return nil, false + return nil, false, nil } priv, err = userenckey.Decrypt(encKeys, []byte(password)) if err != nil { 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) - return &passdbResponse{PrivateKey: b64encode(priv)}, true + return &passdbResponse{PrivateKey: b64encode(priv)}, true, nil } func b64encode(b []byte) string { diff --git a/server/keystore.go b/server/keystore.go index 9cc820b3..af4f9fe7 100644 --- a/server/keystore.go +++ b/server/keystore.go @@ -23,7 +23,7 @@ var ( // Database represents the interface to the underlying backend for // encrypted user keys. type Database interface { - GetPrivateKeys(context.Context, string) [][]byte + GetPrivateKeys(context.Context, string) ([][]byte, error) } type userKey struct { @@ -133,7 +133,10 @@ func (s *KeyStore) Open(ctx context.Context, username, password string, ttlSecon return ErrInvalidTTL } - encKeys := s.db.GetPrivateKeys(ctx, username) + encKeys, err := s.db.GetPrivateKeys(ctx, username) + if err != nil { + return err + } if len(encKeys) == 0 { return ErrNoKeys } -- GitLab