Commit fce7b71c authored by ale's avatar ale

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
Pipeline #802 failed with stages
in 25 seconds
......@@ -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
}
......@@ -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)
}
......
......@@ -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 {
......
......@@ -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
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment