-
-
Notifications
You must be signed in to change notification settings - Fork 734
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
feat: Vault dynamic secrets Generator #2074
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greetings!
Thank you for contributing to this project!
If this is your first time contributing, please make
sure to read the Developer and Contributing Process guides.
Please also mind and follow our Code of Conduct.
Useful commands:
make fmt
: Formats the codemake check-diff
: Ensures the branch is cleanmake reviewable
: Ensures a PR is ready for review
Hi @gusfcarvalho @moolen, I've drafted this based on our discussion in #2036. Would anybody be able to give me a hand with the tests of the new code, please? I've copied over a skeleton for the tests from the provider code, but I cannot seem to get tests including a non-empty mocked Vault response to work. My Golang skills are not very advanced so I'll appreciate any help. Thanks a lot 🙂 |
Thanks, i'm taking a look at it right now 👀 |
return nil, fmt.Errorf(errGetSecret, fmt.Errorf("Empty response from Vault")) | ||
} | ||
|
||
response := make(map[string][]byte) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The response can be deeply nested, this should be a map[string]interface{}
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to make this work, but the Generator interface requires the map[string][]byte
return type.
The current setup means nested fields are stored in the target Secret
as their Go string representation (for example, map[contents:read metadata:read]
). Perhaps I could improve that further by JSON.Marshal
ling them?
That way, the current Generate
interface type can be respected, and target users can parse the required values from JSON as needed (while top-level string values would be readily available to use).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at it 👀 this sounds reasonable. It doesn't really make sense to change the interface, as the target Secret can only hold simple key/value pairs, not nested values.
Nested values are pretty rarely used in vault AFAIK, so 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a look at this again, i think this will help marshaling the map into something useful.
The problem with the current approach is, that you end up with an encoded string, which is a bit unwieldy to work with.
Notice the quotes "
and encoded \n
characters
apiVersion: v1
data:
# ["-----BEGIN CERTIFICATE-----\nMIIDW
ca_chain: WyItLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS1cbk1JSURXe...
# "-----BEGIN CERTIFICATE-----\nMIIDWTCCAkGg
certificate: Ii0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLVxuTUlJRFdUQ0NBa0dn...
# 1678564972
expiration: MTY3ODU2NDk3Mg==
# "-----BEGIN CERTIFICATE-----\nMIIDWzCCAkOgAwIBAgI
issuing_ca: Ii0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLVxuTUlJRFd6Q0NBa09nQXdJQkFnSV...
# "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCA
private_key: Ii0tLS0tQkVHSU4gUlNBIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUVvd0lCQUFLQ0F...
# "rsa"
private_key_type: InJzYSI=
serial_number: IjUxOjIzOjg3OmNhOmI5OmU5OjBjOjdmO...
immutable: false
kind: Secret
metadata:
name: pki-example-com
type: Opaque
I don't think the function mentioned above supports a complex type []string
like the ca_chain
field, thay may need some tweaking and tests 🔨
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @moolen, I've pushed an update that makes use of (and extends) the function you mentioned (works for me for github
-type dynamic secret in live environment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The approach looks good so far!
I did a bit of research about the vault API surface, it looks like there are a couple of APIs we need to support for this generator. We don't need to support everything right away but we need to design it in a way so we can support it at some point in the future.
With this PR we support all GET-based APIs:
GET /aws/creds/:name
GET /azure/creds/:name
GET /consul/creds/:name
GET /rabbitmq/creds/:name
GET /database/creds/:name
GET /kubernetes/creds/:role
GET /terraform/creds/:name
However, we don't support POST-based APIs like:
POST /aws/sts/:name
GET/POST /gcp/impersonated-account/:impersonated-account/token
GET/POST /gcp/static-account/:static-account/token
GET/POST /gcp/roleset/:roleset/token
POST /pki/issue/:name
POST /pki/issuer/:issuer_ref/issue/:name
POST /ssh/creds/:name
POST /ssh/issue/:name
POST /transit/datakey/:type/:name
POST /transit/random(/:source)(/:bytes)
POST /transit/hmac/:name(/:algorithm)
POST /kmip/scope/:scope/role/:role/credential/generate
With that being said i think we need a way to pass two things: a method
and parameters
:
apiVersion: generators.external-secrets.io/v1alpha1
kind: VaultDynamicSecret
spec:
path: "/pki/issue/baz"
# which method to use when making the API call
# one of: GET, POST, PATCH, LIST, DELETE. defaults to GET
#
method: "POST"
# a map[string]interface{}
# that is passed as-is to the vault API
parameters:
common_name: "foobar"
uri_sans: "..."
ip_sans: "..."
# renamed this to "provider"
provider:
server: ""
caBundle: ""
auth:
kubernetes:
mountPath: ""
role: ""
serviceAccountRef:
name: "foo"
Do you consider to implement that in scope of this PR? It would be great to have that covered.
Hi @moolen, can you please re-check? I've adjusted the code according to your comments (and added JSON marshalling for the nested output - please see my comment above). Thanks a lot 🙂 |
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
I did a quick manual test with vault locally, let me try to document the steps taken: setup (roughly)
Dynamic Secret + ExternalSecret apiVersion: generators.external-secrets.io/v1alpha1
kind: VaultDynamicSecret
metadata:
name: "pki-example"
spec:
path: "/pki/issue/example-dot-com"
method: "POST"
parameters:
common_name: "localhost"
ip_sans: "127.0.0.1,127.0.0.11"
provider:
server: "http://vault.default.svc.cluster.local:8200" # localhost:8200 when running port-forward
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets-operator"
serviceAccountRef:
name: "default"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: "pki-example-com"
spec:
refreshInterval: "768h"
target:
name: pki-example-com
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: VaultDynamicSecret
name: "pki-example" resulting secret: apiVersion: v1
data:
ca_chain: WyItLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS1cbk1JSURXe...
certificate: Ii0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLVxuTUlJRFdUQ0NBa0dn...
expiration: MTY3ODU2NDk3Mg==
issuing_ca: Ii0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLVxuTUlJRFd6Q0NBa09nQXdJQkFnSV...
private_key: Ii0tLS0tQkVHSU4gUlNBIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUVvd0lCQUFLQ0F...
private_key_type: InJzYSI=
serial_number: IjUxOjIzOjg3OmNhOmI5OmU5OjBjOjdmO...
immutable: false
kind: Secret
metadata:
name: pki-example-com
type: Opaque |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some minor comments, can you address those? Otherwise it's just some linter issues that should be straight-forward to fix.
I really like that there are minimal changes in pkg/provider/vault
👍
It's really well structured and easy to read 🏅
pkg/provider/vault
needs some serious refactoring 👩🔧, but thats a different topic 😉
Hi @moolen, thanks for the latest comments! I've integrated them into the PR. A note: moving the Thanks a lot 🙂 |
Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did another test run, LGTM! 🎖️
Thank you so much for this contribution ❤️
Do you fancy to write some 📖 docs for this? If not i can do it before we release this, no problem.
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com>
Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com>
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com>
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
…xternal-secrets into vault-generator
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
@gusfcarvalho @knelasevero anyone wants to take look at this PR? If there are no objections i'd be happy to merge this in 🤞 |
Been looking at this PR from a distance 👀 - didn't have the time to properly play with it, but it already feels very good 🥳 I believe we would only be missing vault tokens themselves (as in a short lived vault credential for other apps to consume) in order to be compliant with every possible use case vault could have - which IMO it makes sense to just be another Generator 😄 |
Hi @moolen, thanks for the approval & further fixes! I can definitely try to prepare some docs; just two questions please:
Thanks a lot 🙂 |
Let's make an extra PR for that. I'd add a new entry to the api/components section [1] and add a note to the Vault provider that states that the intended use is for vault kv only, for other backends point to the new generator section. |
This should be covered with |
Thanks for the merge @moolen :) I'll try to prepare the docs shortly 🚀 |
thank you! We'll do a release this week 🚀 |
* docs: add HashiCorp Vault Generator documentation Document the Vault dynamic secrets Generator from #2074. Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> * fix: add vault generator to nav Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> --------- Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com>
* feat: Vault dynamic secrets Generator Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> * Update pkg/provider/vault/vault.go Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com> * feat: Vault dynamic secrets Generator Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> * Update pkg/provider/vault/vault.go Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com> * fix: linter Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> --------- Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <moolen@users.noreply.github.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Aleem Isiaka <aleemisiaka@gmail.com>
) * docs: add HashiCorp Vault Generator documentation Document the Vault dynamic secrets Generator from external-secrets#2074. Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> * fix: add vault generator to nav Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> --------- Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Aleem Isiaka <aleemisiaka@gmail.com>
* feat: Vault dynamic secrets Generator Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> * Update pkg/provider/vault/vault.go Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com> * feat: Vault dynamic secrets Generator Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> * Update pkg/provider/vault/vault.go Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com> * fix: linter Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> --------- Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> Signed-off-by: Moritz Johner <moolen@users.noreply.github.com> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <moolen@users.noreply.github.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Aleem Isiaka <aleemisiaka@gmail.com>
) * docs: add HashiCorp Vault Generator documentation Document the Vault dynamic secrets Generator from external-secrets#2074. Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> * fix: add vault generator to nav Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> --------- Signed-off-by: Kristián Leško <kristian.lesko@gooddata.com> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com> Signed-off-by: Aleem Isiaka <aleemisiaka@gmail.com>
Anyone got this working successfully and can provide a full example? I am having issues getting it to work. In fact, applying the following manifest:
crashes the external-secrets Pod:
|
You found a bug! Can you try this: add apiVersion: generators.external-secrets.io/v1alpha1
kind: VaultDynamicSecret
metadata:
name: "vault-token"
spec:
path: "/v1/auth/token/create/"
method: "POST"
parameters: {} # add this here
provider:
server: "https://vault.mycompany.de"
version: "v1"
auth:
kubernetes:
mountPath: "main-cluster"
role: "baseproject-project-x"
serviceAccountRef:
name: "baseproject"
|
This will be fixed with the upcoming release, see 👆 |
This prevents the crash. But still does not yield a working result. |
Hey @norman-zon i'll need some time to reproduce this, would you mind creating an issue so we can track that? 🙏 |
I reported the problem in #2324 |
Problem Statement
The existing Vault provider is well suited for reading static Vault key-value secrets into k8s Secrets; however, reading dynamic secrets is not supported. This Generator is an attempt to extend
external-secrets
with that functionality.Related Issue
Based on a discussion in #2036.
Proposed Changes
Add a new
VaultDynamicSecret
API object with an associated Generator module.Extend the current
pkg/provider/vault
code with the possibility to initialize a Vault client from the Generator.Checklist
git commit --signoff
make test
make reviewable