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

Secrets: write-up best practices, do's and don'ts, roadmap #13490

Open
thaJeztah opened this issue May 26, 2015 · 203 comments
Open

Secrets: write-up best practices, do's and don'ts, roadmap #13490

thaJeztah opened this issue May 26, 2015 · 203 comments

Comments

@thaJeztah
Copy link
Member

@thaJeztah thaJeztah commented May 26, 2015

Handling secrets (passwords, keys and related) in Docker is a recurring topic. Many pull-requests have been 'hijacked' by people wanting to (mis)use a specific feature for handling secrets.

So far, we only discourage people to use those features, because they're either provenly insecure, or not designed for handling secrets, hence "possibly" insecure. We don't offer them real alternatives, at least, not for all situations and if, then without a practical example.

I just think "secrets" is something that has been left lingering for too long. This results in users (mis)using features that are not designed for this (with the side effect that discussions get polluted with feature requests in this area) and making them jump through hoops just to be able to work with secrets.

Features / hacks that are (mis)used for secrets

This list is probably incomplete, but worth a mention

  • Environment Variables. Probably the most used, because it's part of the "12 factor app". Environment variables are discouraged, because they are;
    • Accessible by any proces in the container, thus easily "leaked"
    • Preserved in intermediate layers of an image, and visible in docker inspect
    • Shared with any container linked to the container
  • Build-time environment variables (#9176, #15182). The build-time environment variables were not designed to handle secrets. By lack of other options, people are planning to use them for this. To prevent giving the impression that they are suitable for secrets, it's been decided to deliberately not encrypt those variables in the process.
  • Mark .. Squash / Flatten layers. (#332, #12198, #4232, #9591). Squashing layers will remove the intermediate layers from the final image, however, secrets used in those intermediate layers will still end up in the build cache.
  • Volumes. IIRC some people were able to use the fact that volumes are re-created for each build-step, allowing them to store secrets. I'm not sure this actually works, and can't find the reference to how that's done.
  • Manually building containers. Skip using a Dockerfile and manually build a container, commiting the results to an image
  • Custom Hacks. For example, hosting secrets on a server, curl-ing the secrets and remove them afterwards, all in a single layer. (also see https://github.com/dockito/vault)

So, what's needed?

  • Add documentation on "do's" and "don'ts" when dealing with secrets; @diogomonica made some excellent points in #9176 (comment)
  • Describe the officially "endorsed" / approved way to handle secrets, if possible, using the current features
  • Provide roadmap / design for officially handling secrets, we may want to make this pluggable, so that we don't have to re-invent the wheel and use existing offerings in this area, for example, Vault, Keywiz, Sneaker

The above should be written / designed with both build-time and run-time secrets in mind

@calavera created a quick-and-dirty proof-of-concept on how the new Volume-Drivers (#13161) could be used for this; https://github.com/calavera/docker-volume-keywhiz-fs

Note: Environment variables are used as the de-facto standard to pass configuration/settings, including secrets to containers. This includes official images on Docker Hub (e.g. MySQL, WordPress, PostgreSQL). These images should adopt the new 'best practices' when written/implemented.

In good tradition, here are some older proposals for handling secrets;

  • "Add private files support" #5836
  • "Add secret store" #6075
  • "Continuation of the docker secret storage feature" #6697
  • "Proposal: The Docker Vault" #10310
@thaJeztah
Copy link
Member Author

@thaJeztah thaJeztah commented May 26, 2015

ping @ewindisch @diogomonica @NathanMcCauley This is just a quick write-up. Feel free to modify/update the description if you think that's nescessary :)

@dreamcat4
Copy link

@dreamcat4 dreamcat4 commented May 26, 2015

This is useful infos:

hashicorp/vault#165

As is this:

hashicorp/vault#164

@thaJeztah
Copy link
Member Author

@thaJeztah thaJeztah commented May 26, 2015

@dreamcat4 there are some plans to implement a generic "secrets API", which would allow you to use either Vault, or Keywiz or you-name-it with Docker, but all in the same way. It's just an early thought, so it will require additional research.

@dreamcat4
Copy link

@dreamcat4 dreamcat4 commented May 27, 2015

@thaJeztah Yep Sorry I don't want to detract from those efforts / discussion in any way. I am more thinking maybe it's a useful exercise also (as part of that longer process and while we are waiting) to see how far we can get right now. Then it shows up more clearly to others the limits and deficiencies in current process. What underlying is missing and needed the most to be added to improve the secrets.

Also it's worth considering about the different situations of run-time secrets VS build-time secrets. For which there is also an area overlap area.

And perhaps also (for docker) we may also be worth to consider limitations (pros/cons) between solutions that provide a mechanism to handle the secrets "in-memory". As opposed to a more heavily file-based secrets methods or network based ones e.g. local secrets server. Which are the current hacks on the table (until proper secrets API). This can help us to understand some of the unique value (for example of stronger security) added by a docker secrets API which could not otherwise be achieved by using hacks on top of the current docker feature set. However I am not a security expert. So I cannot really comment on those things with such a great certainty.

@thaJeztah
Copy link
Member Author

@thaJeztah thaJeztah commented May 27, 2015

@dreamcat4 yes, you're right; for the short term, those links are indeed useful.

Also it's worth considering about the different situations of run-time secrets VS build-time secrets. For which there is also an area overlap area.

Thanks! I think I had that in my original description, must have gotten lost in the process. I will add a bullet

However I am not a security expert.

Neither am I, that's why I "pinged" the security maintainers; IMO, this should be something written by them 😇

@diogomonica
Copy link
Contributor

@diogomonica diogomonica commented May 27, 2015

@thaJeztah great summary. I'll try to poke at this whenever I find some time.

@thaJeztah
Copy link
Member Author

@thaJeztah thaJeztah commented Jun 7, 2015

@diogomonica although not directly related, there a long open feature request for forwarding SSH key agent during build; #6396 given the number of comments, it would be good to give that some thinking too. (If even to take a decision on it whether or not it can/should be implemented)

@ebuchman
Copy link

@ebuchman ebuchman commented Jun 13, 2015

Assuming you could mount volumes as user other than root (I know it's impossible, but humour me), would that be a favourable approach to getting secrets into containers?

If so, I'd advocate for an alternative to -v host_dir:image_dir that expects the use of a data-only container and might look like -vc host_dir:image_dir (ie. volume-copy) wherein the contents of host_dir are copied into the image_dir volume on the data-only container.

We could then emphasize a secure-data-only containers paradigm and allow those volumes to be encrypted

@kepkin
Copy link

@kepkin kepkin commented Nov 13, 2015

I've recently read a good article about that from @jrslv where he propose to build a special docker image with secrets just to build your app, and than build another image for distribution using results from running build image.

So you have two Dockerfiles:

  • Dockerfile.build (here you simply copy all your secrets)
  • Dockerfile.dist (this one you will push to registry)

Now we can build our distribution like that:

# !/bin/sh
docker build -t hello-world-build -f Dockerfile.build .
docker run hello-world-build >build.tar.gz 
docker build -t hello-world -f Dockerfile.dist ^

Your secrets are safe, as you never push hello-world-build image.

I recommend to read @jrslv article for more details http://resources.codeship.com/ebooks/continuous-integration-continuous-delivery-with-docker

@lamroger
Copy link

@lamroger lamroger commented Nov 13, 2015

Thanks for sharing @kepkin !
Just finished reading the article. Really concise!

I like the idea of exporting the files and loading them in through a separate Dockerfile. It feels like squashing without the "intermediate layers being in the build cache" issue.

However, I'm nervous that it'll complicate development and might require a third Dockerfile for simplicity.

@TomasTomecek
Copy link
Contributor

@TomasTomecek TomasTomecek commented Nov 24, 2015

@kepkin no offense but that doesn't make any sense. Secrets are definitely not safe, since they are in the tarball and the tarball is being ADDed to production image -- even if you remove the tarball, without squashing, it will leak in some layer.

@thaJeztah
Copy link
Member Author

@thaJeztah thaJeztah commented Nov 24, 2015

@TomasTomecek if I understand the example correctly, the tarball is not the image-layers, but just the binary that was built inside the build container. See for example; https://github.com/docker-library/hello-world/blob/master/update.sh (no secrets involved here, but just a simple example of a build container)

@kepkin
Copy link

@kepkin kepkin commented Nov 25, 2015

@TomasTomecek I'm talking about secrets for building Docker image. For instance, you need to pass ssh key to checkout source code from your private GitHub repository. And the tarball contains only build artifacts but doesn't contain GitHub key.

@TomasTomecek
Copy link
Contributor

@TomasTomecek TomasTomecek commented Nov 25, 2015

@kepkin right, now I read your post again and can see it. Sorry about that. Unfortunately it doesn't solve the issue when you need secrets during deployment/building the distribution image (e.g. fetching artifacts and authenticating with artifact service). But it's definitely a good solution for separation between build process and release process.

@kepkin
Copy link

@kepkin kepkin commented Nov 25, 2015

@TomasTomecek that's exactly how I fetch artifacts actually.

In Docker.build image I download some binary dependencies from Amazon S3 image which require AWS key & secret. After retrieving and building, I create a tarball with everything I need.

@mumoshu
Copy link

@mumoshu mumoshu commented Mar 13, 2018

For anyone interested: I had tried to exploit "masked-by-default" build-args like FTP_PROXY to build contexts. It is safe in regard to the fact that docker-build doesn't expose those masked args to image metadata nor image layers.

#36443 was an attempt to expand it to a build-arg named like SECRET so that we can encourage users to use it as a simple work-around to the secret management problem.

However, the work has been rejected reasonably, as the masked nature of those build-args aren't guaranteed in the future.

My best bet after that is to follow @AkihiroSuda's advice, use docker build --network or a tool like habitus to store/pass secrets via a temporary tcp server only visible build contexts live within single docker daemon, at broadest.

@darmbrust
Copy link

@darmbrust darmbrust commented Jul 9, 2018

Commenting partially, so I get notification 5 years from now, when Docker finally decides to give us a tiny step in the direction of proper credential management.... and also, to give an outline of the hack I'm using at the moment, to help others, or to get holes poked in it that I'm unaware of.

In following @mumoshu issue, I finally got the hint of using the predefined-args for build secrets.

So, essentially, I can use docker-compose, with a mapping like this:

  myProject:
    build:
      context: ../myProject/
      args: 
        - HTTPS_PROXY=${NEXUS_USERNAME}
        - NO_PROXY=${NEXUS_PASSWORD}

And then, in folder with the docker-compose.yml file, create a file named ".env" with key-value pairs of NEXUS_USERNAME and NEXUS_PASSWORD - and the proper values there.

Finally, in the Dockerfile itself, we specify our run command like so:
RUN wget --user $HTTPS_PROXY --password $NO_PROXY

And do NOT declare those as ARGs in the DockerFile.

I haven't found my credentials floating in the resulting build anywhere yet... but I don't know if I'm looking everywhere..... And for the rest of the developers on my project, they just each have to create the .env file with the proper values for them.

@sameer-kumar
Copy link

@sameer-kumar sameer-kumar commented Jul 25, 2018

@darmbrust I tried your solution but couldn't make it to work.
Here is my compose yml:
version: "3.3"
services:

  buildToolsImage:
    image: vsbuildtools2017:web-v6
    
    build:
      context: .
      dockerfile: ./vsbuild-web-v6-optimized.dockerfile
      args:
        - CONTAINER_USER_PWD=${CONTAINER_USER_CREDS}

Here is .env file sitting next to yml file:

CONTAINER_USER_CREDS=secretpassword

And, here is my dockerfile:

# escape=`
FROM microsoft/dotnet-framework:4.7.2-sdk
# Add non-root user
CMD ["sh", "-c", "echo ${CONTAINER_USER_PWD}"] 
RUN net user userone ${CONTAINER_USER_PWD} /add /Y

And finally the command to kick this off is like this:

docker-compose -f docker-compose.buildImage.yml build

It builds the image but without using the password stored in .env file.

[Warning] One or more build-args [CONTAINER_USER_PWD] were not consumed

What am I missing here?
Thanks!

@darmbrust
Copy link

@darmbrust darmbrust commented Jul 25, 2018

You have to use one of the https://docs.docker.com/engine/reference/builder/#predefined-args in the docker file. You can't use your own argument names like CONTAINER_USER_PWD.

That's how the trick works, cause docker has special behavior for the predefined-args, in that you can use them without declaring them. And by using them without declaring them, they don't appear to be logged anywhere.

With the docker-compose file, you can map those predefined args to something more reasonably named.

@sameer-kumar
Copy link

@sameer-kumar sameer-kumar commented Jul 26, 2018

@darmbrust Yes, that did the trick.
However, don't you think its smelly? Any better recommendations?
Thanks!

@binarytemple
Copy link

@binarytemple binarytemple commented Jul 26, 2018

@darmbrust
Copy link

@darmbrust darmbrust commented Jul 26, 2018

This entire bug is smelly. I haven't found a better way... there are several other approaches above, but I think all of the other secure ones require standing up a little http server to feed the information into the image. maybe less smelly, but more complexity, more tools, more moving parts.

Not sure that anyone has found a "good" solution... we are all stuck waiting on the docker people to do something about it... don't hold your breath, since this bug was written in 2015, and they haven't even proposed a roadmap yet, much less a solution.

@binarytemple
Copy link

@binarytemple binarytemple commented Jul 26, 2018

@cpuguy83
Copy link
Contributor

@cpuguy83 cpuguy83 commented Jul 26, 2018

@binarytemple Everyone who has ever worked on Docker/moby (as in the engineers behind it) know exactly what the problem is and have even run up against it.

Volumes is a solution that is itself incredibly leaky.
There is a proposal, mentioned up the comment stream a bit, that attempts to solve this in a reasonable manner (#33343)

The main thing here is providing the "right" abstraction rather than "any abstraction that happens to work"... we of course know this is painful for many in more than just this case.

Lots of work has been done on the builder lately which isn't necessarily visible yet, but the fruits of this effort will begin to show up in coming months.
To begin with, Docker 18.06 ships with an alternative builder implementation backed by https://github.com/moby/buildkit.
You may think "how does this help me?". Buildkit provides a lot of low-level primitives that enables us to be much more flexible in the Docker builder. Even so much as to be able to provide your own build parser (which can be anything from an enhanced Dockerfile parser to something completely different). Parsers are specified at the top of the "Dockerfile" and are just any image you want to use to parse the file.

If you really want to see something right now, you can take buildkit itself and run with it today, it sits on top of containerd, you can build a custom integration pretty quickly.

@tonistiigi
Copy link
Member

@tonistiigi tonistiigi commented Jul 26, 2018

Secret mounts support was added to buildkit in moby/buildkit#522 . They appear strictly on tmpfs, are excluded from build cache and can use a configurable data source. No PR yet that exposes it in a dockerfile syntax but should be a simple addition.

@BenoitNorrin
Copy link

@BenoitNorrin BenoitNorrin commented Jul 27, 2018

There are 2 solutions to build images with secrets.

Multi-stage build :

FROM ubuntu as intermediate
ARG USERNAME
ARG PASSWORD
RUN git clone https://${USERNAME}:${PASSWORD}@github.com/username/repository.git

FROM ubuntu
# copy the repository form the previous image
COPY --from=intermediate /your-repo /srv/your-repo

Then : docker build --build-arg USERNAME=username --build-arg PASSWORD=password my-image .

Using a image builder : docker-build-with-secrets

@binarytemple
Copy link

@binarytemple binarytemple commented Jul 27, 2018

@BenoitNorrin sorry, but you've exposed that password to every process on the host system. Unix security 101 - don't put secrets as command arguments.

@BenoitNorrin
Copy link

@BenoitNorrin BenoitNorrin commented Jul 27, 2018

Yes but there are some usages where security matters a little less:

  • you want to build on your own computer
  • you build on your entreprise CI server (like jenkins). Most of the time it's about having access to a private repository (nexus, git, npm, etc), so your CI may have her own credentials for that.
  • you can use a VM created from docker-machine and remove it after.
@Yajo
Copy link

@Yajo Yajo commented Jul 27, 2018

If that's the only problem, @binarytemple, then simply adding the flag docker image build --args-file ./my-secret-file should be a pretty easy fix for this whole problem, isn't it? 🤔

@binarytemple
Copy link

@binarytemple binarytemple commented Jul 27, 2018

@Yajo could be, yes it's at least a workaround until buildkit ships with secrets mount. Good suggestion. Thanks. B

@pvanderlinden
Copy link

@pvanderlinden pvanderlinden commented Jul 27, 2018

Unfortunately most of the workarounds mentioned in these and the many other tickets still expose the secrets to the resulting image, or only works with specific languages where you only need dependencies during compile time and not during installation.

@binarytemple that will never happen, the docker maintainers have already killed at least one PR fully documented and fully implemented of a safe secret feature. Given the rest of history (this 3 year old ticket isn't the oldest and definitely not the only ticket/PR on this topic) I think it's safe to say the docker maintainers don't understand the need for security, which is a big problem.

@caub
Copy link

@caub caub commented Aug 14, 2018

The biggest pain point is secret rotations for me

you've to maintain a graph of secret to services dependencies, and update twice each service (to get back to original secret name)

listing secrets from services doesn't seem to be easy (I gave up after some attempts around docker service inspect --format='{{.Spec.TaskTemplate.ContainerSpec.Secrets}}' <some_service>), listing services dependencies from docker secret inspect <secret_name> would be useful too. So I just maintain that (approximate) graph manually for now.

You also have to specify the secret destination, when it's not the default /run/secrets/<secret_name> in the docker service update command

I just hope for a simpler way to rotate secrets

@BretFisher
Copy link

@BretFisher BretFisher commented Aug 14, 2018

@caub here's some CLI help:

Docker docs for formatting help come up with the rest of your inspect format:

docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Secrets}}{{println .SecretName}}{{end}}'

That'll list all secret names in a service. If you wanted both name and ID, you could:

docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Secrets}}{{println .SecretName .SecretID}}{{end}}' nginx

I always have my CI/CD (service update commands) or stack files hardcode the path so you don't have that issue on rotation.

With labels you can have CI/CD automation identify the right secret if you're not using stack files (without needing the secret name, which would be different each time).

@AkihiroSuda
Copy link
Member

@AkihiroSuda AkihiroSuda commented Nov 11, 2018

docker build --secret is finally available in Docker 18.09 https://medium.com/@tonistiigi/build-secrets-and-ssh-forwarding-in-docker-18-09-ae8161d066

@thaJeztah Are we ready to close this issue?

@andriy-f
Copy link

@andriy-f andriy-f commented Nov 11, 2018

For older versions of docker, using multistage build with copying secrets before build command is viable option, right?

FROM debian as build
COPY ./secret.conf /path/on/image/
RUN build.sh
...

FROM debian
COPY --from=build ...
@thaJeztah
Copy link
Member Author

@thaJeztah thaJeztah commented Nov 11, 2018

@andriy-f yes, that works, as long as you;

  • (obviously) don't copy the secret to the final stage 😉, or:
  • use the build stage / stage in which a secret is present as a "parent" for the final image
  • never push the build-stage to a registry
  • trust the host on which your daemon runs; i.e. taking into account that your "build" stage is preserved as an image; someone with access to that image would be able to get access to your secret.
@thaJeztah
Copy link
Member Author

@thaJeztah thaJeztah commented Sep 17, 2020

build time secrets are now possible when using buildkit as builder; see the blog post here https://medium.com/@tonistiigi/build-secrets-and-ssh-forwarding-in-docker-18-09-ae8161d066

and the documentation; https://docs.docker.com/develop/develop-images/build_enhancements/

the RUN --mount option used for secrets will graduate to the default (stable) Dockerfile syntax soon

@pdemro
Copy link

@pdemro pdemro commented Sep 17, 2020

Thank you @thaJeztah I did just a little more digging and found that article shortly after posting (previous post is now deleted). Thanks again!

@Vanuan
Copy link

@Vanuan Vanuan commented Sep 18, 2020

Cool. That closes the build time secrets question. Anything for runtime/devtime (ssh in OS X)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.