keystore
KeyStore holds unencrypted secrets on behalf of users in memory for
a short time (of the order of a SSO session lifespan). User secrets
can be opened with a password (used to decrypt the key, which is
stored encrypted in a database), queried by presenting a suitable
authentication token, and closed (wiped and forgotten).
The database can provide multiple versions of the encrypted key (to
support multiple decryption passwords), in which case we'll try
them all sequentially until one of them decrypts successfully with
the provided password.
In order to query the KeyStore, you need to present a valid SSO
token for the user whose secrets you would like to obtain.
API
The server exports an API over HTTP/HTTPS. All requests should be made
using the POST method and a Content-Type of application/json. The
request body should contain a JSON-encoded object. Responses will be
similarly JSON-encoded.
/api/open
(OpenRequest)
Retrieve the encrypted key for a user, decrypt it with the provided
password, and store it in memory.
OpenRequest is an object with the following attributes:
username
password
to decrypt the user's key with
ttl
(seconds) time after which the credentials are automatically
forgotten
If the user has no encrypted keys in the database, the request will
still return successfully: no action will be performed, and no errors
will be returned.
/api/get
(GetRequest) -> GetResponse
Retrieve the key for a user. GetRequest must contain the following
attributes:
username
whose key you wish to retrieve
sso_ticket
with a valid SSO ticket for the keystore service
If the request is successfully authenticated, GetResponse will contain
a single attribute key.
/api/close
(CloseRequest)
Forget the key for a given user.
Dovecot integration
The final consumer for user encryption keys is the Dovecot
service. The dovecot-keylookupd daemon can read the user public and
private keys from the database, and serve the unencrypted keys to
Dovecot using its dict proxy
protocol.
NOTE that passdb lookups using dovecot-keylookupd contain the
cleartext password as part of the key, which may be logged in case of
error! This is currently a huge limitation of this solution, but there
seems to be no workaround that does not involve switching to a
fork()-based solution (like the checkpassword script). That might be a
better solution long-term.
TODO: explain the lookup protocol.
Configuration
The keystored daemon loads its configuration from a YAML-encoded
file, /etc/keystore/config.yml by default. It can contain the
following attributes:
sso_public_key_file
: path to the SSO Ed25519 public key
sso_service
: SSO service for this application
sso_domain
: SSO domain
backend
: backend configuration
type
: backend type, one of ldap or sql
params
: backend parameters, type-specific (see Backend
configuration, below)
http_server
: HTTP server configuration
tls
: contains the server-side TLS configuration:
cert
: path to the server certificate
key
: path to the server's private key
ca
: path to the CA used to validate clients
acl
: specifies TLS-based access controls, a list of entries
with the following attributes:
path
: regular expression to match the request URL path
cn
: regular expression that must match the CommonName part
of the subject of the client certificate
max_inflight_requests
: maximum number of in-flight requests to
allow before server-side throttling kicks in
The dovecot-keylookupd daemon uses a similar configuration, read by
default from /etc/keystore/dovecot.yml:
backend
: backend configuration
type
: backend type, one of ldap or sql
params
: backend parameters, type-specific (see Backend
configuration, below)
keystore
: configures the connection to the keystore service
url
: URL for the keystore service
sharded
: if true, requests to the keystore service will be
partitioned according to the user's shard attribute
tls
: client TLS configuration
cert
: path to the client certificate
key
: path to the private key
ca
: path to the CA used to validate the server
shard
: shard identifier for the local host. Must be set if
keystore.sharded is true.
Backend configuration
The keystore servers can talk to a LDAP or a SQL database. In both
cases it is possible to adapt to the database schema by defining the
exact queries to use. All we need to do is to retrieve the public and
private parts of the user encryption key.
The ldap database backend understands the following configuration
parameters:
uri
: LDAP server URI
bind_dn
: bind DN (for simple bind, SASL is not supported)
bind_pw
: bind password
bind_pw_file
: bind password (load from this file), in
alternative to bind_pw
query
: Parameters for the LDAP search query
search_base
: base DN for the search
search_filter
: search filter. The filter string may contain a
literal %s
token somewhere, that will be replaced with the
(escaped) username.
scope
: search scope, one of sub (default), one or base
public_key_attr
: attribute that contains the user's public key
private_key_attr
: attribute that contains the user's encrypted
key(s)
The sql database backend requires the following parameters:
driver
: SQL driver, one of sqlite3, mysql or postgres
db_uri
: database URI (a.k.a. DSN), whose exact syntax will depend
on the chosen driver. Check out the documentation for the
database/sql sqlite,
mysql and
postgres drivers.
queries
: map with the known queries. All SQL queries take one
parameter (the user name), and return one or more rows with a single
column. Use the ?
placeholder for the parameter. Known queries:
get_user_public_key
: must return a single row with the public
key
get_user_private_keys
: must return one or more rows with the
user's private keys (copies of the same key encrypted with
different passwords).