-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Secure environment variables encrypted in Yaml #1145
Comments
cc @shawnzhu |
note that this implementation uses AES256 encryption because all parameters are encrypted via the endpoint. We use a per-repo encryption key ( I'm hesitant to use rsa encryption because the |
And how do we get the encrypted value that have to be placed in the yaml? |
@tboerger one idea is creating a new sub-command for drone CLI to get the encrypted version: $ drone encrypt <owner/repo-name> <sensitive-value>
U2FsdGVkX18B3tY76uPxLe2tA6UzaBrVMMsArAUS4bo= |
great if users can access the cli command standalone :) |
the way over new drone CLI sub command is just one way to enable user to encrypt sensitive data locally without sending to drone server. it also provided a new endpoint |
@tboerger we'll also expose something in the UI to generate the encrypted strings, for those that don't have the CLI installed
@shawnzhu I think both approaches have their pros and cons. If we use EncryptOAEP we'll need to send the public key and secret hash down to the client to encrypt. If a team member uploads their own rsa public and private keys to the repository, it is possible a team member can decrypt any secrets I put in the yaml. If we use AES we'll need to send the secret over the network and encrypt via the REST API. The key used to AES encrypt secrets is never shared or exposed to the end user. This means I could add a secret to a yaml and none of my team members would be able to decrypt it [1]. So it seems to come down to which you trust more, your network or your team members? I don't think there is any right answer here. I'm definitely open to more dialog. [1] we currently don't inject any secrets in the build in 0.4 so that people can't echo them or leak them. Secrets are only sent to plugins, which are much more limited in functionality, and generally cannot execute arbitrary commands. Not sure if this will change / need to change in the future. |
@bradrydzewski my thought is the key difference is whether it allows user (repo owner) to inject their own encryption key (private key for RSA, or repo secret hash for AES). because one should trust team members (when adding them to administrate a hosted git repo) who have the equal privilege to maintain sensitive data. I would prefer to:
then we can keep existing AES based solution with an RSA solution. users should not care about this since it's generated by either a drone sub command or web form |
important to note that the hash is used to sign the hook URL added to github etc. changing the hash would invalidate the hook, so we'll need to re-generate the hook
the reason we added this feature was to allow people to share a key across multiple repos. There were a few different use cases, but the biggest was to avoid having to manually move deploy-keys to the user account-level to enable cloning other private repos (when using git submodules, npm deps from private repos, etc). So we would break one feature to add another :( This was a highly requested feature, so I'm sure it is a good idea to remove Either way we're going to be sending decrypted secrets over the network. Even if we implement RSA encryption we still end up sending the decrypted values to the Docker daemon when launching the build (over tcp). The alternative is sending the encrypted values to the Docker daemon with the Key and Hash (still over tcp). So it seems using RSA only solves 1/2 of the problem :( Any thoughts how to resolve the second half? |
Yes, which means drone must update hook URL if repo hash is changed. but if one repo Hash never be leaked to client side, no need to expose any user interface to regenerate it. I'm fine to leave it is another topic about trust issue between drone-build and docker machines where builds run. Docker itself enables TLS from transportation layer in default (it is another thing if drone administrator disable this). according to application layer, transferring encrypted values is acceptable. The only input I have is keeping shared Hash and/or Key possessed in a secure store (libtrust?). Anyway, specific operational security requirement to drone + docker would be a good starting point. |
@shawnzhu agreed, but if a team is very security conscious perhaps they should run drone with SSL? If you aren't running drone with SSL, an eavesdropper would have the ability to intercept the authorization token or intercept the GitHub access token at login and gain access to your GitHub account ... I'm mostly playing devils advocate here (I don't mean to be difficult) but I want to understand if the real answer is "use SSL if you need strong security" ... if yes, we should heavily promote SSL in the documentation. |
@bradrydzewski yes, SSL/TLS should be default option to secure endpoint over HTTP. Per this question I found this from twitter API doc:
BTW, drone may use |
@shawnzhu also may make sense to consider: Instead of using the RSA keys, we could generate an alternate set of keys strictly used for private variables. Secretbox seems like a pretty interesting alternative here |
|
here is an example using it doesn't use |
@shawnzhu this is way outside my area of expertise, but AES uses a 128 bit to 256 bit key and it is considered pretty hard (impossible?) to brute force. Wondering if this is an issue for box. |
I see #1154 and I really like taking the sha256 of |
yes, this is what I thought. the downside is, technically the ciphertext (never change) of RSA OAEP leaked and it won't change until Actually this is because I tried my best to get a |
@shawnzhu awesome work. Testing it now, we may need to use I'll play around with it more tomorrow |
@shawnzhu after some yaml parser issues I adjusted to use Works like a charm 😄 I've been thinking about your prior comment regarding embedding the variable names in the encrypted string itself. So for example, instead of this: secure:
- DOCKER_PASS: WsQVT0g...
- DOCKER_PASS: ECLQAnlq... Doing this: secure:
- WsQVT0g...
- ECLQAnlq... I think this may actually be possible. We would still need to return a Would love to hear your thoughts. Also, I'm going to add a form to the UI so that people can generate encrypted strings without having the CLI installed. The CLI will still remain the recommended way to encrypt, so this will be more for convenience. Once I add the UI functionality, and we decide on whether to embed the parameter names in the encrypted string, or keep external, we can close this one out! |
I don't like the variable included within secret. After half of a year nobody remembers which variables are already part of it |
@tboerger great point, I can see how that would get annoying. As a side not, you will still see
would alternatively look like this:
so you can still see which private tokens are required by looking at the |
I think the confusion will be bigger than benefit :) What if you want to remove the encrypted value? You don't know which one :) |
I've run into one issue with RSA keys. In some cases developers want to encrypt large strings, for example, other keys or certificates used to sign and publish Android applications. RSA can only encrypt a maximum of 245 bytes 😢 |
this should be a limitation of
but the number 245 (256 minus 11) looks like the limitation of
do you think it is a good idea to improve the implementation by splitting long message into array of 245 bytes bytes array? |
What if the solution was to offer both options, and leave it to users to determine what is most appropriate for their project? This would likely provide the most future-proof solution, too, as it would necessitate choosing a format for the encryption details that'd be flexible enough to accommodate different schemes, which provides both backwards and forward compatibility. One option would be using one of the JOSE standards as the container for the encryption details and content, and then putting that entire JSON object (Base64 encoded, or otherwise) directly into the .drone.yml's secure content area. There's a Go library already for this standard (h/t @linuxwolf) that could be leveraged: https://github.com/square/go-jose I know that Drone currently uses RSA for public keys, but has there been any investigation into using something elliptic curve-based instead? I'm not sure if that causes compatibility issues with GitHub, or other VCSes. |
I have a friend (cc @hildjj) who mentioned that he was using this functionality in Travis CI. Might be interesting to get some feedback from somebody who's used the system in their environment. It looks like the Travis implementation in their CLI is just using RSA directly... I guess it works, but it feels like it could be restrictive. |
@benschumacher successfully summons me. First: travis gets this really right. Their job is easier because they have a fixed service to talk to. However, you might be able to use the public key information you've already got from the cert that you offer for either TLS or SSH, which makes this not as bad as you might think. Second, get a crypto person to help you. They're going to ask you what your threat model is first, so be prepared to answer that. A start:
That that person will likely tell you: just use JOSE. You MUST do some public-key crypto here. AES alone likely doesn't solve the use case (but you'll actually use both RSA and AES before you're done). Then you'll want to add algorithm agility so you can support elliptic curves. JOSE has all of that stuff layered correctly out of the box: https://tools.ietf.org/html/rfc7516 Next, please include the variable name in the plaintext, even if you feel like you must repeat it outside as a label for maintainability. I'm worried about them being able to assign the plaintext to a new variable, and use that to exfiltrate data or attack unexpected parts of the system. Note that your command-line tool might be able to make an API call to list the variable names relatively safely. |
@benschumacher @hildjj thanks for the input and I really like the suggestion to go down the JOSE path. I need to familiarize myself with the specification a bit more, but the Go implementation that @benschumacher referenced looks pretty solid.
Yes, we can use the keys generated for SSH here
The drone implementation works slightly differently than Travis. The secure parameters are only provided to publish, deploy and notification plugins. Each plugin runs in its own Docker container. The main build container, which can execute arbitrary commands and arbitrary code, won't have access to secure variables to overwrite, alter or echo. Regardless, we can include the variable names in the plaintext. I've received feedback from a few others that would prefer this approach as well. Thanks again for jumping in here, much appreciated! |
This is an excellent point, and I hadn't thought about this. Being able to move the encrypted portion from one variable to another could expose a secret intended for one plugin container to another. It does make the data appear more opaque, but this could be addressed via comments in the YAML. |
This also has me thinking ... What if the encrypted string included the plugin / step for which it was intended? This would prevent us from sending secrets to a plugin that shouldn't receive them. For example, let's say we encrypted the following string:
When Drone executes a build, we could ensure notify:
slack:
- image: plugins/slack
+ image: evil-plugins/slack
token: $$slack_token
channel: foo We prevent the use of secrets in pull requests for this reason.. Travis does as well. We also white-list plugins for this reason. But ... since Drone will only send secrets to separate plugin containers, and never injects in the build environment ... if we can verify it is sent to the correct, trusted plugin, we should be able to safely enable secrets in pull requests. (hopefully that made sense) |
I really like plugin whitelist feature which means it won't download evil plugins in drone via using docker hub trusted build. However, the following steps should be a threat:
So I would say it should always re-pull plugin image in https://github.com/drone/drone/blob/0.4.0/cmd/drone-build/client.go#L94-L95 to make sure it always download from trusted docker registry. |
This isn't going away. We'll definitely keep this functionality
The The ability to build and publish Docker images in 0.4 no longer requires a privileged build container, and no longer allows access to the host machines Docker daemon. In 0.4, we have a special plugin that runs Docker in Docker to avoid this exploit. This plugin can only run a pre-defined set of commands ( There are plenty of potential exploits that we need to consider and I'm very glad you're thinking about them -- and we should document them all -- but probably in a separate issue or markdown file in |
@bradrydzewski I like that. I'd suggest that since you're Base64-protected already, you could use NULL's to separate the tokens (or better yet, length encode) in order to reduce the chances of parser attacks (what happens when someone puts in a plugin or variable name containing pipe or equal?).
|
related to this issue, #1189 is a proposal by @benschumacher that we discuss and I've drafted. I would love to get some feedback. |
moving discussion to #1189 |
Currently we have the ability to store private environment variables in the database. I'd like to expand this capability to store encrypted environment variables in the yaml itself, similar to travis.
the secure parameters are decrypted at runtime and injected into your build yaml, similar to how we inject private variables (entered in the UI) or matrix parameters:
Unlike travis, injecting the private parameters as environment variables is not sufficient since publish, deploy and notify sections (which are typically the only sections that require such secrets) are executed outside of the build container and expect very specific parameters in the yaml. This is a result of the new 0.4 plugin model.
So for now we need to inject using the same syntax as matrix and private variables. I will spend some time, however, thinking up alternate approaches ...
The text was updated successfully, but these errors were encountered: