Skip to content

Proposal: Docker Engine Keys for Docker Remote API Authentication and Authorization #7667

@jlhawn

Description

@jlhawn

Motivation

Currently, client to daemon authentication over a TCP port can only be achieved through generating TLS certificates for both the client and daemon. Each daemon instance then needs to be configured to use the generated TLS certificate and the client must specify its own certificate as well. Production critical, large-scale deployments should already be using this method to secure and control access to Docker daemons, but the extra setup required by generating your own keys, getting them signed by a certificate authority, and distributing those certificates is too much overhead for setting up small-scale deployments such as a Boot2Docker VM running on a developer's Mac, for example. Software developers are already familiar with how SSH key distribution works: through a list of authorized_keys on the server and known_host keys on the client. Ideally each instance of the Docker engine (client or daemon) would have a unique identity represented by its own public key. With a list of trusted public keys, two engines can authenticate to eachother and the daemon can authorize the connection. This can be done at the TLS layer after initially loading a list of trusted public keys into a CA Pool.

Proposal Summary

Every instance of Docker will have its own public key which it either generates and saves on first run or loads from a file on subsequent runs. The public key will be distributed to other instances by a user of docker or system administrator to allow connections between two docker engines. Each instance will have a list of public keys which are trusted to accept connections from (trusted clients) and a separate list which it trusts to make connections to (trusted hosts). These public keys will be stored as JSON Web Keys and can be distributed as a JSON file, or as a standard PEM file. For TLS connections, the Docker engine's key pair will be used to generate a self-signed TLS certificate and the list of public keys will be used to generate a certificate pool with a certificate authority for each public key. For TLS servers the list of public keys will be loaded from an authorization file (authorized_keys.json) and for TLS clients the list will be loaded from a known hosts file (allowed_hosts.json), a client must always provide its certificate if the daemon requires it. In addition, a certificate authority PEM file will be allowed to be specified to maintain the existing TLS behavior. As another possible addition, upon connecting to a previously unknown server, a CLI user can be prompted to allow a public key now and in the future, leaving it up the user’s discretion.

Key Files

Docker will support key files in either JSON Web Key format or more traditional PEM format.

Private and Public Key files

Both the docker daemon and client will have a private key file and a public key file in either of these formats. A client's private key default location will be at ~/.docker/key.(pem|json|jwk) and public key at ~/.docker/pub_key.(pem|json|jwk) where ~ is the home directory of the user running the docker client. The daemon's private key default location will be at /etc/docker/key.(pem|json|jwk) and public key at /etc/docker/pub_key.(pem|json|jwk). Unix file permissions for these private keys MUST be set to 0600, or 'Read/Write only by User'. It is suggested that the public keys have permissions set to 0644, or 'Read/Write by User, Read by group/others'. Because these keys may have a variable file extension, Docker will load whichever one matches the glob key.* first, so it is NOT RECOMMENDED that there be multiple key.* files to avoid any ambiguity over which key file will be used. If the --tlskey=KEYFILE argument is used, that exact file will be used. Optionally, we may add a config file for Docker client and daemon in which users may specify the file to use, but that possibility is up for discussion.

Example Private Key using JSON Web Key format:

{
    "comment": "My Docker Key",
    "crv": "P-256",
    "d": "D00we1lvii5JRuD_FbunAsVxJoSurE3eMAyG-p1U_bo",
    "kid": "YM2K:C6TY:2V27:DRZO:LTFC:L5L3:A6GZ:KVOV:BLEN:6P72:2YMB:7LQJ",
    "kty": "EC",
    "x": "8qmksN-_VZuRMFdXhzc0kpCyOh3mnyulBFdsq0vMpUE",
    "y": "uNds3LDn05Y7UOUePfOS9qATKfXsCKUPep-pBn32aE4"
}

Example Private Key using PEM format:

-----BEGIN EC PRIVATE KEY-----
comment: My Docker Key
keyID: PI6I:3UVA:5FXA:KUS6:DJOV:A6X6:HVET:T5HR:7WKY:45ZL:JXOO:FLFU

MHcCAQEEICKzsxR5bPJOsONaXcIUvDfT5v56zA5f+Tnqxjute633oAoGCCqGSM49
AwEHoUQDQgAEhCsfa2wxQbYt+eIH2O0nEQ1+5fdz81wbnZc8r2UpBKqBQJQ1AGnD
WnlsuUy0rRrw1kSUwcW9WvhEoHGEGrTKnw==
-----END EC PRIVATE KEY-----

Example Public Key using JSON Web Key format:

{
    "comment": "My Docker Key",
    "crv": "P-256",
    "kid": "YM2K:C6TY:2V27:DRZO:LTFC:L5L3:A6GZ:KVOV:BLEN:6P72:2YMB:7LQJ",
    "kty": "EC",
    "x": "8qmksN-_VZuRMFdXhzc0kpCyOh3mnyulBFdsq0vMpUE",
    "y": "uNds3LDn05Y7UOUePfOS9qATKfXsCKUPep-pBn32aE4"
}

Example Public Key using PEM format:

-----BEGIN PUBLIC KEY-----
comment: My Docker Key
keyID: PI6I:3UVA:5FXA:KUS6:DJOV:A6X6:HVET:T5HR:7WKY:45ZL:JXOO:FLFU

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhCsfa2wxQbYt+eIH2O0nEQ1+5fdz
81wbnZc8r2UpBKqBQJQ1AGnDWnlsuUy0rRrw1kSUwcW9WvhEoHGEGrTKnw==
-----END PUBLIC KEY-----
Authorized Keys file

An instance of the Docker engine in daemon mode will need to know which clients are authorized to connect. We propose a file which contains a list of public keys which are authorized to access the Docker Remote API. This idea is borrowed from SSH's authorized_keys file. Any client which has the corresponding private key for any public key in this list will be able to connect. This is accomplished by generating a Certificate Authority Pool with a CA certificate automatically generated by the daemon for each key in this list. The server's TLS configuration will allow clients which present a self-signed certificate using one of these keys. Like today, the daemon can still be configured to use a traditional Certificate Authority (the --tlscacert=CACERTFILE option). The default location for this file will be /etc/docker/authorized_keys.(pem|json|jwk). Docker will also look for trusted client keys in individual files in a directory at /etc/docker/authorized_keys.d in either PEM or JWK format.

Example Authorized Keys file using JSON Web Key Set format:

{
    "keys": [
        {
            "comment": "Demo Client A",
            "crv": "P-256",
            "kid": "JGVF:PQA4:NC5N:KWQY:3E7I:BI5V:QH6L:ZM3W:IIF6:6WNQ:LS3Q:IOYC",
            "kty": "EC",
            "x": "NEVMqNRwBF6mPWITr7pFWN2vL1DBVQLwBYSrvL79Y2g",
            "y": "eufpD4nTxcZ2hp-sbyuLImQFQE9jjuZtsUnpdukKgAc"
        },
        {
            "comment": "Demo Client B",
            "crv": "P-256",
            "kid": "YM2K:C6TY:2V27:DRZO:LTFC:L5L3:A6GZ:KVOV:BLEN:6P72:2YMB:7LQJ",
            "kty": "EC",
            "x": "8qmksN-_VZuRMFdXhzc0kpCyOh3mnyulBFdsq0vMpUE",
            "y": "uNds3LDn05Y7UOUePfOS9qATKfXsCKUPep-pBn32aE4"
        }
    ]
}

Example Authorized Keys file using PEM Bundle format:

-----BEGIN PUBLIC KEY-----
comment: Demo Client A
keyID: 3YHT:AUOJ:KMSD:4VJ5:7XHT:I375:7KXA:LBTD:KSWW:HICE:AAMH:MRSU

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUPOCQPjwK5BmTYjjtQUuWBCpW5ER
p/kBNNxwA88qs/XlG3uppzm53HMTRBGZIZn3cv4C/OQItDkm8hFYzvZekw==
-----END PUBLIC KEY-----
-----BEGIN PUBLIC KEY-----
comment: Demo Client B
keyID: PI6I:3UVA:5FXA:KUS6:DJOV:A6X6:HVET:T5HR:7WKY:45ZL:JXOO:FLFU

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhCsfa2wxQbYt+eIH2O0nEQ1+5fdz
81wbnZc8r2UpBKqBQJQ1AGnDWnlsuUy0rRrw1kSUwcW9WvhEoHGEGrTKnw==
-----END PUBLIC KEY-----
Trusted Hosts file

An instance of the Docker engine in client mode will need to know which hosts it trusts to connect to. We propose a file which contains a list of public keys which the client trusts to be the key of the Docker Remote API server it wishes to connect to. This idea is borrowed from SSH's know_hosts file. Any daemon which has the corresponding private key for a public key in this list AND presents a self-signed server certificate in the TLS handshake which has the desired server name (hostname or IP address of $DOCKER_HOST) using one of these keys. Like today, the client can still be configured to use a traditional Certificate Authority (the --tlscacert=CACERTFILE option). The TCP address (in the form of <hostname_or_ip>:<port>) will be specified for each key using extended attributes for the key, i.e, a address JSON field if in JWK format or a address header if in PEM format. The default location for this file will be ~/.docker/trusted_hosts.(pem|json|jwk). Docker will also look for trusted host keys in individual files in a directory at ~/.docker/trusted_hosts.d in either PEM or JWK format.

Example Trusted Hosts file using JSON Web Key Set format:

{
    "keys": [
        {
            "crv": "P-256",
            "hosts": [
                "localhost",
                "docker.example.com"
            ],
            "kid": "OQNF:PCXT:FAYS:SDTY:ZHXZ:SLDD:3PY3:V3GI:URTX:BDXQ:TQDW:CRQ6",
            "kty": "EC",
            "x": "mc1wSZWrrgBOsWBg3XXYiuL8vhBNdoMZANkk2hvj8-g",
            "y": "SsIVJn5VZzuCigKkuqIl7EPdFTnCU5TSR-gD6DkxSG8"
        },
        {
            "crv": "P-256",
            "hosts": [
                "localhost",
                "docker.example.com"
            ],
            "kid": "KUPF:4OZR:MZAE:GJAT:HOJR:PGE4:ORNX:AXEM:5OSL:7IC6:HSAD:EOPH",
            "kty": "EC",
            "x": "aDP4a11PJjtMuOZd9C2PIAOs37l1AMHMxIok5Ie3jhY",
            "y": "ARm9r8eGmc575viOZKU4hXzHKPzwnClDuiNcGlHdyrU"
        }
    ]
}

Example Trusted Hosts file using PEM Bundle format:

-----BEGIN PUBLIC KEY-----
hosts: localhost,docker.example.com
keyID: ZIIF:X7DE:LYA4:SCQH:XYRH:EM7X:SLED:IIUU:XVVP:FSRN:QZ4T:3VMQ

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhTTP5gZlLUETXGFvVaDrZSBilr6P
RDZ28kji+nbjQo+kh3c9rG4ath+fukTag2LaqnluxiJPUxCTsj3R9MfEVA==
-----END PUBLIC KEY-----
-----BEGIN PUBLIC KEY-----
hosts: localhost,docker.example.com
keyID: KM54:SCR6:NVJX:PKBB:A5YN:BWMO:P455:VCLD:KF22:TRAL:YSJC:JXOD

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELANz7WzbZdK/foHvF8sR4S4IzCwz
WfgrXHD5a+eiu2uXC6RJj/durYTuFhJOdGOtBJJJpmLkLQpnlWv2YAW0ZQ==
-----END PUBLIC KEY-----

Key Types

By default, a Docker engine will generate an ECDSA key, using the standard P-256 elliptic curve, if a private key file does not already exist. Supported Elliptic Curves are P-256, P-384, and P-521. RSA keys are also supported. The default of Elliptic Curve Cryptography was chosen due to more efficient key generation and smaller key sizes for equivalent levels of security when compared to RSA [reference].

User visible changes

  • TLS is always used for when using tcp:// (unix:// does not require)
  • Client TLS verification is on by default (--insecure flag added to disable)
  • Server TLS verification is on by default (--insecure flag added to disable)
  • --tls and --tlsverify flags removed
  • -i/--identity flag to specify the identity (private key) file
  • User prompt added when connecting to unknown server

Backwards Compatibility

In order to maintain backwards compatibility, existing TLS ca, cert, and key options for setting up TLS connections will be allowed. Scripts using --tls and --tlsverify will need to remove these options since these are now the default. To use the existing insecure behavior, run scripts will need to be modified to use --insecure, this is not recommended. These changes do no have any effect on servers using unix sockets.

  • Connecting from older client: The client must generate a certificate which is distributed to the server. Optionally the newer server can run with --insecure which will require no changes to the client.
  • Connecting to an older server: If non-TLS, Client will maintain ability to connect to endpoint using the --insecure flag. If TLS is manually configured, no changes should be required.

Usage Pattern

  • Single Machine - Setup using Unix socket, no changes
  • Single Machine (with non-B2D VM) -
    • Invoke docker on host to generate key.json
    • Invoke docker on guest to generate key.json
    • Copy ~/.docker/pub_key.json on guest to /etc/docker/trusted_hosts.d/guest.json on host
    • Copy /etc/docker/pub_key.json on host to ~/.docker/authorized_keys.d/host.json on guest (optionally use prompt)
  • Single Machine (B2D) - Boot2Docker installation generates and copies keys
  • Two Machines -
    • Invoke docker on client to generate key.json
    • Invoke docker on server to generate key.json
    • Copy ~/.docker/pub_key.json on client to ~/.docker/authorized_keys.d/client.json on server
    • Copy /etc/docker/pub_key.json on server to ~/.docker/trusted_hosts.d/server.json on client

Updated

Updated location for files for server from /var/lib/docker to /etc/docker

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/securitykind/featureFunctionality or other elements that the project doesn't currently have. Features are new and shiny

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions