README.md 11.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

auth-server
===========

A low-level authentication server with pluggable backends and some
advanced features:

* two-factor authentication support (TOTP, U2F)
* application-specific passwords
* rate limiting and brute force protection
* new device detection

Its purpose is to be the single point of authentication for all
authentication flows in a service.

# Deployment

The auth-server is fully stateless: it delegates state to other
backends such as Memcached for short-term storage, and
[usermetadb](https://git.autistici.org/id/usermetadb) for long-term
anonymized user activity data. For this reason, it is recommended to
install an auth-server on every host.

It listens for authorization requests over a UNIX socket. UNIX
permissions should be used to control access to the socket if
necessary. Clients speak a custom simple line-based attribute/value
protocol, and can send multiple requests over the same connection.

## Services

A *service* in auth-server is a specific scope for an authentication
workflow, normally associated with a specific user-facing
service. Multiple services can be defined, each with its own
functionality and user backends.

## User backends

The authentication server data model is based on the concept of a
*user account*. The server knows how to retrieve user accounts stored
in LDAP, but it has to be told the specific details of how to find
them and how to map the information there to what it needs.

ale's avatar
ale committed
43 44 45 46 47 48 49 50 51 52 53 54
## Other Dependencies

The auth-server can optionally use *memcached* to store short-term
data with a relatively high probability of retrieval. This is used to
store U2F challenges, and used OTP tokens for replay protection. If no
memcache servers are configured, such functionality will be disabled
but the auth-server will still run (useful for tests, or simpler
deployments).

It is possible to specify multiple memcached servers for HA purposes,
with a *write-all / read-any* model.

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
# Configuration

The behavior of auth-server can be configured with a YAML file.
The YAML file should contain a dictionary with the following attributes:

* `services` is a dictionary describing all known services and their
  authentication parameters. See the *Service definition* section below
* `services_dir` (optional) points at a directory containing service
  configuration. Besides describing services in the main configuration
  file (using the `services` attribute), it is possible to define
  additional services in YAML-encoded files (having a *.yml*
  extension), which is more automation-friendly.
* `rate_limits` defines the global rate limiters and blacklists. See
  the *Rate limiting* section below.
* `enabled_backends` is the list of user backends that should be
  enabled (the available backends are *file* and *ldap*)
* `ldap_config` specifies the configuration for the LDAP backend --
  see the *LDAP Backend* section below
* `user_meta_server` holds the configuration for the user-meta-server
  backend:
  * `url` is the URL of the service
  * `tls` configures TLS for the client:
    * `cert` is the path to the client certificate
    * `key` is the path to the client private key
    * `ca` is the path to the CA store to verify the server certificate
ale's avatar
ale committed
80 81
* `memcache_servers` contains a list of memcached server addresses (in
  host:port format)
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

## Rate limiting

Rate limits and blacklists are global (available to all services), to
allow brute force protection to work across multiple services. The
top-level configuration attribute `rate_limits` is a dictionary of
named rate limiting configurations, that can be later referenced in
the service-specific `rate_limits` list. Each rate limiter definition
should specify the following attributes:

* `limit` counts the number of events to allow over a period of time
* `period` defines the period of time
* `blacklist_for` adds the client to a blacklist if their request rate
  goes above the specified threshold
* `on_failure` is a boolean value, when true the rate limiter will
  only be applied to failed authentication requests
* `keys` is a list of strings specifying the request identifiers that
  will make up the rate limiter key. The list can include one or both
  of *ip* (referring to the remote client's IP) and *user* (username)

## Service definition

Each service definition is a dictionary with the following attributes:

* `backends` is a list of user backend specifications, each one should
  include a backend-specific configuration under an attribute named
  after the backend itself:
  * `file` is simply a path to a user list file, see the *File
    backend* section below
  * `ldap` configues the LDAP backend for this service
  * `static_groups` is a list of group names that users sourced from
    this backend will automatically be added to
* `challenge_response` is a boolean parameter that, when true, enables
  two-factor authentication for this service (it should be enabled
  only for interactive services)
* `enforce_2fa` is a boolean flag that, when true, will disable
  non-2FA logins for this service
* `enable_device_tracking` is a boolean flag that enables device
  tracking for this service (assuming the client provides device
  information)
* `rate_limits` is a list of names of global rate limiters to be
  applied to this service.

## File backend

The *file* backend reads users and their credentials from a
YAML-encoded file. This file should contain a list of dictionaries,
each representing a user, with the following attributes:

* `name` is the username
* `email` is the email associated with the user (optional)
* `password` stores the encrypted password
* `totp_secret` stores the *unencrypted* TOTP secret seed
* `groups` is a list of group names that the user belongs to

The file backend only supports TOTP as a two-factor authentication
method, U2F support is currently missing.

## LDAP Backend

The *ldap* backend will look up user information in a LDAP database.
It needs some parameters to be passed in the top-level *ldap_config*
dictionary:

* `uri` of the LDAP server (like *ldapi:///var/run/ldap/ldapi*)
* `bind_dn` is the DN to bind with
* `bind_pw_file` points at a file containing the bind password

The *ldap* backend will currently always attempt to bind on every
connection.

### Query definition

LDAP queries are meant to return a single user account object from the
database using a *search* operation. There's two parts to it: first
the right object needs to be located, then we need to map the object's
attributes to someting that the auth-server understands.

The LDAP query for a service is defined by the following standard LDAP
parameters:

* `search_base` specifies a base DN for the search
* `search_filter` specifies a filter to apply to the search
* `scope` specifies the scope of the LDAP search, must be one of
  *base*, *one* or *sub*
* `attrs` is a dictionary mapping LDAP attributes to their auth-server
  metadata counterparts, see *Schema definition* below.

The `search_filter` should contain somewhere the literal string `%s`,
which will be replaced with the username in the final LDAP query.

### Schema definition

In order to retrieve authentication information from the LDAP object,
the authentication server needs to know which attributes to use. To do
so, we use a so-called *schema definition* (a map of symbolic names to
LDAP attributes). The following attribute names are defined:

* `password` contains the encrypted password. Since this attribute is
  often also used for authentication of the LDAP protocol itself, an
  eventual `{crypt}` prefix is ignored. Passwords should be encrypted.
* `otp_secret` should contain the hex-encoded TOTP secret
* `app_specific_password` (possibly repeated) contains an encrypted
  app-specific password

The default attribute mapping looks like this:

    password: userPassword
    totp_secret: totpSecret
    app_specific_password: appSpecificPassword

Except for *userPassword*, the others are custom LDAP attributes and
are not part of any standard schema definition. You should create your
own.

App-specific passwords should be encoded as colon-separated strings:

    service:encrypted_password:comment

The password should be encrypted. The comment is a free-form string
set by the user to tell the various credentials apart.


# Usage

The *auth-server* runs on a local UNIX socket. You can use UNIX
permissions to control who has access to this socket. The Debian
package makes it group-readable to the *auth-server* group, so you can
add specific users to it easily.

The daemon can run either standalone or be socket-activated by
systemd, which is what the Debian package does.

## Wire protocol

The rationale behind the wire protocol ("why not http?") is twofold:
first, we wanted strict access control, and that's more easily done
with UNIX permissions, so UNIX sockets were chosen. Then, the protocol
should be able to transfer data maps, and it must be trivial to
implement (and verify) in C, Go and Python. Furthermore, it should
minimize external dependencies.

The protocol is line-based: multiple authentication requests can be
sent over the same connection, but every request must wait for a
response (i.e. no pipelining). Commands are single words, and can be
followed by a space and an attribute/value map. The responses are
simply attribute/value maps.

Attribute maps should have the following characteristics:

* maps can't be nested, they are simple key/value sets where both keys
  and values are strings
* keys can't contain '=' characters

They are encoded using the following algorithm:

* if this is not the first attribute/value pair, add a space character
* add the key string
* add the '=' character
* if the value contains a non-printable character or a double quote:
  * add the base64-encoded value
* if it does not:
  * add a '"' character, then the value, then another '"' character.

## API

There is only one command: `auth`, which must be followed by the
authentication request. Parameters for an authentication request are:

* `service`: the service requesting the authentication
* `username`: name of the user to authenticate
* `password`: password (cleartext) provided by the user
* `otp` (optional): TOTP-based 2FA token
* `u2f_app_id` (optional): U2F AppID
* `u2f_response` (optional): U2F response object
  * `key_handle`
  * `signature_data`
  * `client_data`
* `device` (optional): information about the client device
  * `id`: a unique ID, specific to this device
  * `remote_addr`: remote IP address (will be minimized)
  * `remote_zone`: remote zone (country-level IP aggregation)
  * `browser`: browser name
  * `os`: client OS
  * `user_agent`: client User-Agent string
  * `mobile`: boolean variable indicating a mobile device

Responses will contain the following attributes:

* `status`: status of the request, one of *ok*,
  *insufficient_credentials* or *error*
* `2fa_method`: if *status* is *insufficient_credentials*, one of
  *otp* or *u2f* indicating which 2FA method should be used for the
  next request
* `u2f_req`: when *2fa_method* is *u2f*, this field will contain a U2F
  SignResponse object:
  * `version`
  * `challenge`
  * `registered_keys`: a list of registered keys
* `user`: when *status* is *ok* (the authentication has been
  successful), this dictionary will contain user information:
  * `email`: email of this user
  * `groups`: groups the user is a member of.