Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Kerberos support to Docker #13697

Closed
dimastopel opened this issue Jun 3, 2015 · 55 comments
Closed

Adding Kerberos support to Docker #13697

dimastopel opened this issue Jun 3, 2015 · 55 comments
Labels
area/security kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny

Comments

@dimastopel
Copy link
Contributor

Hi Guys

We develop a container security suite oriented to the enterprises. As part of it we support Kerberos as an authentication mechanism between Docker CLI and the Docker daemon. We currently have it up and running but, honestly, think it should be part of the platform. Hence, we would like to contribute the code to Docker.

Today we use Nalin's GSS Go wrapper (https://github.com/nalind/gss) over vanilla MIT Kerberos (https://github.com/krb5/krb5).

I'd like to propose the following, simple, addition to the command line interface that enables generating Kerberos ticket (TGS) on the client (CLI), and require validating Kerberos ticket on the daemon.

CLI example:

docker -H docker-host:8080 --kerbverify --kerbspn=<service_name> ps

Docker daemon example:

docker -d --kerbverify --kerbspn=<service_name / *> --kerbkeytab=/tmp/docker_keytab

The SPN argument is mandatory. However, keytab argument is not. In case keytab is not specified, a default path is used. The default path is: /etc/krb5.keytab (as in MIT krb5)

All variables can be configured using environmental variables:

DOCKER_KERB_VERIFY
DOCKER_KERB_SPN
DOCKER_KERB_KEYTAB

We will support MIT krb5 and Microsoft Active Directory as KDC.

I would be glad to hear you general thoughts and if make sense I'll send a more elaborated technical descriptions of what we'd like to do.

@duglin
Copy link
Contributor

duglin commented Jun 3, 2015

@dimastopel is the ticket sent to the daemon via an http header?

@dimastopel
Copy link
Contributor Author

@duglin Yes, HTTP Authorization header

@duglin
Copy link
Contributor

duglin commented Jun 3, 2015

@dimastopel you may want to look at how the config file allows you to specify additional http headers for all CLI->Daemon flows: https://github.com/docker/docker/blob/master/cliconfig/config.go#L43
This was specifically added for these types of authentication usecases. For our use, we have a "login" type of command (separate exe) that will populate the config file with the headers we need, then the user can do normal docker commands w/o needing to do anything special, like add new flags to each command.

Ideally, we'd like to have a CLI plugin do that and have docker login ... be what the user sees.
See #13140

@NathanMcCauley
Copy link
Contributor

@dimastopel this is a good direction to consider, I'd be interested in more elaboration on the technical design behind this. Especially interested in the daemon side handling of the ticket and access control that would be based on the ticket there.

@dimastopel
Copy link
Contributor Author

@NathanMcCauley sure thing. We are working on this and I'll share a more elaborated story in the next couple of days.

@duglin thanks. Will check this out.

@dimastopel
Copy link
Contributor Author

@NathanMcCauley

Below is a more technical elaboration on how I see each component will operate. It doesn't contain the integration points yet.

Client

In case --kerbverify is specified and SPN is provided, the following steps will be performed within the CLI client:

  1. Obtain the current user credentials. This is basically the Ticket Granting Ticket (TGT) that already exists on the system.
  2. Obtain the ticket to authenticate against the service. This is the Ticket Granting Service (TGS). Unless cached, obtaining TGS requires contacting the Key Distribution Center (KDC). The communication is transparent to the user.
  3. Encoding TGS using base64 as sending as a payload via HTTP Authorization header to the daemon as part of the request.

In case of any error during the process, the client exits with the appropriate error code and message.

Example request header with the token:

Authorization: Negotiate YWRranNsY...==

Daemon

Upon receiving the request the server extracts the TGS from the HTTP Authorization header and validates it. No communication with KDC is required to validate the TGS. Once the TGS is validated, and the User Principal Name (UPN) is extracted the authentication process completes.

In case the authentication process fails an HTTP 401 code is returned to the client. In case the SPN was incorrect HTTP 403 will be returned.

Kerberos authentication requires one hop between the client and the daemon.

Access Control

We use authentication both for auditing but also to provide the ability to create policies that control user access. For example, limit each developer to 5 containers max on the specific server. See below an example screen for configuring such policy.

I propose a thin framework that will enable adding external modules via Docker HTTP API. The modules are external services that expose HTTP REST API. The framework will enable registering to system events, so that each module will be able to register for the relevant event groups. However the framework will also enable the modules to control the flow of the system via return codes. This implies that the calls to the modules will be blocking unless (configurable) timeout occurs. In our case we will register for user authenticated event and check against the policies we have whether this specific user is authorized to perform the specific task. If we see that the user is not allowed to do the task we need to be able to tell the daemon to stop processing the request and provide the appropriate reason.

I didn't investigate yet to what extent the current events infra can be used to achieve this. I guess the current events infra is very relevant. Conceptually, this is also close to what @duglin suggests for CLI.

I think the line between the platform (Docker) and an external partner (e.g., Twistlock) should be this very framework. The framework is part of the platform but the modules are supplied by the external partner. As part of the partner's installation process the module adds itself via Docker's daemon (future) API.

Last note on this. In my opinion, we should differentiate between Kerberos authentication support and access control, and concentrate first on Kerberos authentication only. Once we have this we would be glad to go further with Access Control support. We would be glad to be the once to drive and implement both.

Docker Policy Example

Your comments on this are very welcome.

@thaJeztah
Copy link
Member

I realise this is less important for the global concept of this proposal, but I would prefer to use a more generic flag to pass the options, similar to the way options for logging and graph-drivers are passed. That way other authentication mechanisms can be added in future without introducing new flags.

Something like;

docker -d --auth-opt kerb-verify=... --auth-opt kerb-spn=...

(perhaps even kerberos-verify=...)

we should differentiate between Kerberos authentication support and access control, and concentrate first on Kerberos authentication only.

Fully agree on that; access control should not be tied to authentication here.

I ❤️ the looks of that screenshot though!

@dimastopel
Copy link
Contributor Author

@thaJeztah - fully agree with your proposal re flags.

If there are no major objections to what I have proposed I’m going to draft a pull request with the initial implementation and continue the discussion from there.

If there are objections please let me know ASAP.

@thaJeztah
Copy link
Member

If there are no major objections to what I have proposed I’m going to draft a pull request with the initial implementation and continue the discussion from there.

Purely based on the discussion above, I don't think there are objections (but please don't hold that against me :)). Creating a draft pull-request usually works well, as that gives the maintainers a bit more insight in the proposed changes.

@NathanMcCauley
Copy link
Contributor

@dimastopel Have you considered what it would look like for this notion of pluggable authentication to be implemented as integration with PAM? Then, a bunch of the existing work on different PAM backends could be utilized by the Docker daemon to allow varied methods of authentication (LDAP, kerberos, OTP, etc).

@dimastopel
Copy link
Contributor Author

@NathanMcCauley Let me look into it.

@nalind
Copy link
Contributor

nalind commented Jun 24, 2015

My guess is that, if we were to call into PAM at all, it would be for implementing the password checking part of the Basic auth scheme at the server.

@dimastopel
Copy link
Contributor Author

Sorry for the late reply on this

@NathanMcCauley

After researching this for some time I agree that PAM looks as a great way to get "pluggable" behavior with a relatively low effort. In addition, numerous existing PAM modules can be used OOB.

It also became clear to me that adding generic PAM support to Docker is a superior task than adding a vanilla Kerberos support. Specifically with Kerberos, PAM might not provide a good solution to the Single Sign On (SSO) functionality, but this issue can be addressed after PAM is in place. This module, for example, claims it solves this issue OOB. I didn't verify it yet, though.

Unless there are objections we will work on the general PAM support and then move to Kerberos.

@nalind

As far as I understand we can get support for more than just basic AuthN in case PAM is in place. E.g., OTP, LDAP, Challenge-Response with Yubico, etc. What do I miss?

@thaJeztah
Copy link
Member

ping @icecrime to check if this lines up with the roadmap

Thanks in advance, @dimastopel !

@nalind
Copy link
Contributor

nalind commented Jun 29, 2015

@dimastopel PAM's generally only going to be of use when you want to ask for information that can be directly input by a user, such as a password which can be used for obtaining Kerberos credentials, and Basic auth more or less works for that case. The PAM model allows modules to prompt and ask other questions, too, which is how it can support things like OTP. When modules start asking for things other than passwords, or asking for more than one piece of information, the limitations of what you can communicate between the client and the server using the Basic scheme start creating problems -- a server implementation that just supplies the user's Basic password whenever modules ask for information during authentication is likely going to be providing the wrong answer to some of those modules.

Going by OpenSSH as an example, full PAM support, which avoids that sort of problem, is only available when using "keyboard-interactive" authentication, while "password" authentication's use of PAM is there as a best-effort for the sake of clients that don't implement "keyboard-interactive". When authentication is being done using a binary wire protocol like the one used in GSSAPI and/or Negotiate, or using public keys, PAM isn't used for the authentication step, and the daemon only calls into PAM's account management function to do an authorization check.

In #13994, which is linked here because it overlaps somewhat, I've been focusing on making client- and server-side implementations of auth schemes pluggable, in a way that would allow the server's Basic implementation to be swapped out, or to have more than one available, calling into all of the built plugins which don't disable themselves until one of them succeeds. On a second branch that builds on that, there's a Basic server implementation that uses libsasl2's sasl_checkpass() function. The libsasl2 library can be configured to send the user names and passwords off to saslauthd, and saslauthd can be configured to use PAM to check them. An implementation that calls directly into libpam could be slotted in next to it.

@icecrime
Copy link
Contributor

icecrime commented Jul 1, 2015

Nothing conflicting with the roadmap from my point of view. I'll leave it to @NathanMcCauley and @diogomonica to see how they want to move forward with this.

@NathanMcCauley
Copy link
Contributor

I support implementing an integration with PAM. Having PAM available for authn cases as well as available for authz eventually for the key-based authentication schemes makes sense. @nalind's #13994 looks like a step in the right direction for hooking up client<->daemon.

@dimastopel
Copy link
Contributor Author

@nalind thanks for the reply!

Can you please elaborate on "a server implementation that just supplies the user's Basic password whenever modules ask for information during authentication is likely going to be providing the wrong answer to some of those modules."? Not sure I understand why server supplies authn information and why wrong answers will be provided.

Regarding text based authentication, as far as I know, PAM supports binary messages and client side modules that enables quite rich customization of the authentication process.

As I understand, your solution provides a plug-in based approach for the authentication of the Docker client against the Docker daemon. Is this correct? PAM provides something that is quite similar. Do you see your solution as an alternative to PAM? If not, how do you see the two solutions working together?

@nalind
Copy link
Contributor

nalind commented Jul 2, 2015

@dimastopel The traditional strategy people have used when trying to support PAM in implementations of protocols like FTP is to call into PAM with a prompter callback that walks the list of passed-in prompts and hands back the user name when the msg_style is PAM_PROMPT_ECHO_ON, and to hands back the password when the msg_style is PAM_PROMPT_ECHO_OFF. You run into weird behavior and bug reports if the dialog needs to be more complicated than that. If the prompt text is required in order for the user to supply the right information (I'm thinking of challenge-response OTP schemes here), and it isn't displayed, or if the user is asked for information before the PAM conversation even starts, it can't work. That's a risk when trying to fit something like PAM into a wire protocol that can't be as flexible as PAM expects it to be. OpenSSH ended up needing to implement keyboard-interactive authentication to be able to properly handle the cases where its password authentication scheme wasn't flexible enough.

The Linux-PAM binary prompt interface isn't widely adopted. Outside of the Linux-PAM tree itself, I see one module (the Openwall pam_userpass module) making use of the constants defined as part of the interface, and no references to it in the source trees of the packages in Fedora that link to libpam. Granted, that's not exhaustive.

The patch I'm proposing makes things pluggable at the level of client and server implementations of authentication schemes - client and server implementations for Basic and Negotiate (or others, though I haven't tried to implement others) can be enabled (or not) at build-time, and the internal interface allows them to disable themselves at startup (for example, if the Negotate server discovers that it has no creds, so that client's won't be told that they can try Negotiate when the server is unable to accept it). The server portion of Basic would be where I'd expect to be calling into libpam. Negotiate, for example, would call into a GSSAPI library.

My personal leaning toward using libsasl2 for the server portion of Basic is based partly on wanting to avoid having to implement that sort of logic in a conversation callback, partly on liking that the process separation we get by having libsasl2 call out to the external saslauthd process reduces possible side-effects, and partly on saslauthd offering a caching option.

@dimastopel
Copy link
Contributor Author

@nalind Makes sense.

Several questions:

  1. libsasl2 seems to support GSSAPI and plenty of other authN mechanisms. Why not to use it for everything, including Negotiate, and not just the Basic part?
  2. In case libsasl2 is used for the Basic part, it can be configured to use PAM (for those who are interested) without modifying Docker at all. This is one example of such setup. Do I understand this correctly?
  3. You mention that you plan to enable / disable authN methods at build time. Why not in run time via the configuration?

@nalind
Copy link
Contributor

nalind commented Jul 7, 2015

@dimastopel Thanks for suffering through my wall of text!

Using libsasl2's GSSAPI support would require some additional pipe-fitting -- to start, we'd have to map from HTTP authentication schemes to SASL mechanisms. While that part looks pretty straightforward for Basic and Negotiate, I don't have a good plan for what we'd do for handling Bearer or OAuth, as there doesn't appear to be a simple mapping between the two sets (going off of http://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml and https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xml). The SASL GSSAPI mechanism specifies Kerberos as the only supported GSSAPI mechanism, while Negotiate specifies SPNEGO, and that could create difficulties if the server side is, or ever becomes, clever enough about validating incoming data.

You read correctly, saslauthd can call into PAM to check the password, though you have the same limitations there that you'd have when calling into PAM directly - the protocol between libsasl2 and saslauthd is a single round trip (the library sends the user, password, SASL realm, and service name, and receives either an "OK" response or an error message), but I hope it's clearer to .

If/when we disable mechanisms at build-time is more of a question of how we're handling library dependencies - if we're trying to gracefully handle the absence of some libraries at build-time, then we can have the build scripts check for the presence of libraries using pkg-config, and set build tags to build things conditionally. If we're not, and we can straight-up require libraries to be present to build, we can build everything in. Either way, the various schemes can check the run-time configuration and either not register themselves at startup, or not participate on a per-request basis. We don't know whether or not libsasl2 and saslauthd are even set up at run-time, and offering a scheme that can't possibly succeed is going to annoy clients (I'm thinking particularly about prompting for passwords, only to have the password check fail every time), so we'd a runtime option regardless.

@dimastopel
Copy link
Contributor Author

@nalind Thanks

PAM limitations are clear. What was important for me to verify is that customer with an existing PAM infra (or who just prefers using PAM) will be able to use it with Docker+libsasl2. And as far as I understand the answer is yes.

I think that we can figure out along the way how to deal with issues like SASL + Bearer, OAuth.

Trying to take a bit more pragmatic approach here: @lioryanko from our team tried to build nalind/docker/daemonauth so we could play with it, identify the gaps, and try to build a mutual plan on closing those gaps. What we see is that some of the +build constraints of authenticators aren't met and they aren't compiled. What is the right way to compile the code?

Basically I think that we need to start with two chunks: 1) add pluggable authN framework with (indirect) PAM support. 2) Add support for Negotiate. Then we can continue with OAuth etc.

WDYT?

@nalind
Copy link
Contributor

nalind commented Jul 7, 2015

Right, the authframework branch is just the framework patch, while daemonauth has that and some things that build on top of it. In the daemonauth branch, the Basic and Negotiate bits are currently conditionalized on whether or not the build scripts detect libsasl2 and the GSSAPI libraries by running pkg-config at build-time, and that we're not static linking, since we don't build Kerberos or Cyrus SASL as static libraries in Fedora. If we have to work around that last part to make things available in static builds, we can deal with that. FWIW, for quick checks, I tend to run 'go build -tags "daemon gssapi libsasl2"' from the top-level 'docker' subdirectory.

@lioryanko
Copy link

@nalind Your code looks good. I'm trying to build it now.

I'm having a bit of trouble building your daemonauth branch (without excluding the server's authenticator modules). When trying to build the way you said (from inside docker's build environment container, built using the Dockerfile which includes your modifications) I got the following errors:

# github.com/docker/docker/daemon/graphdriver/btrfs
../daemon/graphdriver/btrfs/version.go:6:27: fatal error: btrfs/version.h: No such file or directory
 #include <btrfs/version.h>
                           ^
compilation terminated.
# github.com/docker/docker/pkg/devicemapper
could not determine kind of name for C.dm_task_deferred_remove

I tried using 'go build -tags "daemon libsasl2 gssapi btrfs_noversion libdm_no_deferred_remove"' (hope that makes sense). The error I'm having now is:

# pkg-config --cflags libsasl2 krb5-gssapi
Package libsasl2 was not found in the pkg-config search path.
Perhaps you should add the directory containing `libsasl2.pc'
to the PKG_CONFIG_PATH environment variable
No package 'libsasl2' found
pkg-config: exit status 1

From what I can tell it looks like libsasl2-dev package supplied by ubuntu is outdated and doesn't include a package configuration file.

Any thoughts?

@nalind
Copy link
Contributor

nalind commented Jul 8, 2015

@lioryanko It looks like you need a newer version of the btrfs-tools package to get /usr/include/btrfs/version.h, too.

As for libsasl2, replacing #cgo pkg-config: libsasl2 with #cgo LDFLAGS: -lsasl2 should do the trick, since the package places the headers in the default search path. It's less future-proof, I guess, but it appears it's going to necessary for regular builds until we move to using 15.04 or something later as the default build environment. I'll make that change shortly.

@lioryanko
Copy link

@nalind Well, the basic authentication looks good:

root@9253f757ae8a:/go/src/github.com/docker/docker# ./docker/docker ps
Username: fake       
Password: 
Error response from daemon: wrong login/password
root@9253f757ae8a:/go/src/github.com/docker/docker# ./docker/docker ps                        
Username: lior
Password: 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

There are bugs though which I haven't really inspected thoroughly, here's an example:

root@9253f757ae8a:/go/src/github.com/docker/docker/docker# ./docker run -ti busybox /bin/sh
Username: lior
Password: 
Post http:///var/run/docker.sock/v1.20/containers/create: http: ContentLength=968 with Body length 0.
* Are you trying to connect to a TLS-enabled daemon without TLS?
* Is your docker daemon up and running?

Also, when trying to build an image (note that there's no authentication prompt):

root@9253f757ae8a:/go/src/github.com/docker/docker# ./docker/docker build -t inception_image .
Sending build context to Docker daemon 241.4 MBn 557.1 kB

All in all it looks pretty sweet :)

@thaJeztah thaJeztah added the status/needs-attention Calls for a collective discussion during a review session label Oct 16, 2015
@ppadmavilasom
Copy link

@lioryanko it seems you had success with basic auth. i can get htpasswd based auth to work. But so far no success on gssapi or libsasl. I ran the AuthSuite tests and they seem to pass. When i start with a --realm and connect over tcp, i get "An error occurred trying to connect: Failed to authenticate to docker daemon; server offered no authentication methods"
Any pointers?

@nalind
Copy link
Contributor

nalind commented Oct 22, 2015

@ppadmavilasom Try starting the server with debugging enabled. The server doesn't offer Negotiate unless it can acquire acceptor creds at startup (i.e., unless it can read the keytab and there are keys in it), but it should log a debug message if that's the case. If you're using mandatory access control, permissions may be a factor.

If the server is being started with --libsasl2 and --realm but isn't offering Basic, either, then there must have been a problem initializing the SASL library. My best guess would be to check the /etc/sasl2/docker.conf or /usr/lib/sasl2/docker.conf files, both contents and permissions.

@cyphar
Copy link
Contributor

cyphar commented Oct 22, 2015

I'm wondering whether this can just be implemented in the AuthZ plugin (#15365) and the lessons learnt making this could be channeled to improve the current AuthZ proposal. I personally am adverse to adding very specific authorisation systems which aren't compatible with others. While I'm not exceptionally pleased with the AuthZ plugin as it stands, it's probably how we'll end up doing plugin authorisation.

liron-l pushed a commit to twistlock/docker that referenced this issue Oct 28, 2015
…ctionality of the Docker daemon with respect to user authorization. The infrastructure enables registering a set of external authorization plug-in. Each plug-in receives information about the user and the request and decides whether to allow or deny the request. Only in case all plug-ins allow accessing the resource the access is granted.

Each plug-in operates as a separate service, and registers with Docker
through general (plug-ins API)
[https://blog.docker.com/2015/06/extending-docker-with-plugins/]. No
Docker daemon recompilation is required in order to add / remove an
authentication plug-in. Each plug-in is notified twice for each
operation: 1) before the operation is performed and, 2) before the
response is returned to the client. The plug-ins can modify the response
that is returned to the client.

The authorization depends on the authorization effort that takes place
in parallel [moby#13697].

This is the official issue of the authorization effort:
moby#14674

(Here)[https://github.com/rhatdan/docker-rbac] you can find an open
document that discusses a default RBAC plug-in for Docker.
liron-l pushed a commit to twistlock/docker that referenced this issue Nov 26, 2015
…ctionality of the Docker daemon with respect to user authorization. The infrastructure enables registering a set of external authorization plug-in. Each plug-in receives information about the user and the request and decides whether to allow or deny the request. Only in case all plug-ins allow accessing the resource the access is granted.

Each plug-in operates as a separate service, and registers with Docker
through general (plug-ins API)
[https://blog.docker.com/2015/06/extending-docker-with-plugins/]. No
Docker daemon recompilation is required in order to add / remove an
authentication plug-in. Each plug-in is notified twice for each
operation: 1) before the operation is performed and, 2) before the
response is returned to the client. The plug-ins can modify the response
that is returned to the client.

The authorization depends on the authorization effort that takes place
in parallel [moby#13697].

This is the official issue of the authorization effort:
moby#14674

(Here)[https://github.com/rhatdan/docker-rbac] you can find an open
document that discusses a default RBAC plug-in for Docker.

Signed-off-by: Liron Levin <liron@twistlock.com>
Added container create flow test and extended the verification for ps
liron-l pushed a commit to twistlock/docker that referenced this issue Dec 8, 2015
…ctionality of the Docker daemon with respect to user authorization. The infrastructure enables registering a set of external authorization plug-in. Each plug-in receives information about the user and the request and decides whether to allow or deny the request. Only in case all plug-ins allow accessing the resource the access is granted.

Each plug-in operates as a separate service, and registers with Docker
through general (plug-ins API)
[https://blog.docker.com/2015/06/extending-docker-with-plugins/]. No
Docker daemon recompilation is required in order to add / remove an
authentication plug-in. Each plug-in is notified twice for each
operation: 1) before the operation is performed and, 2) before the
response is returned to the client. The plug-ins can modify the response
that is returned to the client.

The authorization depends on the authorization effort that takes place
in parallel [moby#13697].

This is the official issue of the authorization effort:
moby#14674

(Here)[https://github.com/rhatdan/docker-rbac] you can find an open
document that discusses a default RBAC plug-in for Docker.

Signed-off-by: Liron Levin <liron@twistlock.com>
Added container create flow test and extended the verification for ps
@LK4D4
Copy link
Contributor

LK4D4 commented Sep 16, 2016

@nalind @NathanMcCauley do you know what's status here? I see some references from another repos, but not sure how to interpret them.

@thaJeztah
Copy link
Member

Closing this one because it went stale, and probably can be implemented using the AuthZ plugins

@thaJeztah thaJeztah removed the status/needs-attention Calls for a collective discussion during a review session label Sep 21, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/security kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny
Projects
None yet
Development

No branches or pull requests