-
Notifications
You must be signed in to change notification settings - Fork 545
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
Enhance vault_generic_secret for more general API calls #244
Conversation
c358b14
to
f8ccfc4
Compare
I thought I'd also point out that this will be ever more useful after we have |
c953c8c
to
a1d2165
Compare
Got tests to pass. I think I'm right to ignore |
The new functionality is not exercised in any automated test at this time. |
Hello again! How do I get someone to look at this? Sorry if I overlooked something. I notice that @tyrannosaurus-becks has reviewed and merged several recent pull requests. I don't want to nag...I just want to make sure this doesn't languish and that I am following correct procedures. Thanks. :-) |
vault/resource_generic_secret.go
Outdated
if err != nil { | ||
return fmt.Errorf("error marshaling JSON for %q: %s", path, err) | ||
} | ||
d.Set("write_data_json", string(jsonData)) |
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.
Writing the entire secret out gives me pause, though I'm undecided what I think of it yet.
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.
Yeah, it makes me a bit nervous too, actually. I wonder whether there should be an input parameter that specifically indicates which fields should be written.
vault/resource_generic_secret.go
Outdated
if err != nil { | ||
return fmt.Errorf("data_json %#v syntax error: %s", d.Get("data_json"), err) | ||
} | ||
relevantData = suppliedData |
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.
Is this line needed?
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.
Yes, because sometimes fields that are written are not returned by the read operation. Initializing relevantData with suppliedData ensures that those fields' absence from the read operation will not cause terraform to consider the resource to be out of date. In my example, the password
field is an example of this. If you first write to auth/userpass/users/u1
with the password
field and the read back auth/userpass/users/u1
, you get back some fields you didn't write and you don't get back password. This causes password to be persisted in the state, but any user who is using the vault terraform provider must always be mindful of what's being written to state. The use case here would be to use a dummy password that is changed out of band. Let me know if this is clear or if I should clarify further.
vault/resource_generic_secret.go
Outdated
} | ||
d.Set("write_data_json", string(jsonData)) | ||
} else { | ||
d.Set("write_data_json", "null") |
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.
Is this line needed?
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.
Some write operations don't return anything, and I wanted to make sure that a reference to write_data_json
didn't fail in that case. Basically I was trying to follow the pattern I observed that all things that could be read from the resource were explicitly initialized. Please let me know if I have misunderstood how this is supposed to work. I can probably produce an example that would error out if this line were absent. Basically since terraform doesn't have any way to ask whether a particular field is there, it's easier to work with if the same fields are always there but just have explicitly null values. Lazy evaluation of conditionals in 0.12 may make this less problematic.
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.
@jberkenbilt thank you for working on this! I appreciate it!
I'm not certain what I think of this approach yet. I need to play more with the example you've given, comparing how it works today to how it would work with the code. I don't fully grok the use case and how this solves it, though your explanation is excellent.
@tyrannosaurus-becks Thanks for taking a look! What can I do to help you grok the use case? If you think this is worth pursuing, I can find some time to write tests that cover this better, including tests that would fail if some of the lines you had questions about where removed. Also, if you think it's better, I can change it so that instead of recording In 0.12, terraform will have proper support for nested data structures, which makes it unnecessary to bother with json strings. Is there an intention to enhance the interface to store results in proper nested data structures instead of json strings? That would obviously impact this resource. I notice that the I'll try to find some time to add tests, though I will have to familiarize myself with how the tests are set up. Let me know what you think about restricting which parts of the write response are stored, and if you can shed any light on whether interfaces will change for 0.12, that would be helpful. Thanks! |
Adding my 2cents to this discussion: The overloading of I'd be more keen to seeing the |
@cvbarros You make a very good point. This change is, admittedly, a workaround for places where the terraform provider is not yet rich enough to handle several cases. In my own work, I find myself facing similar dilemas frequently. I usually fall on the side of forgoeing the workaround in favor of the right solution, though sometimes the benefit of a simple solution that can be used "now" outweighs the potential downside, especially when the right solution is significantly harder or resources are not available soon. The documentation already touts generic secret as usable for general API calls, but it doesn't really work for the reasons that made me do this. I can think of three options:
I would also point out that something like this would also be potentially useful for working with third-party plugins or new vault features that are not yet in the terraform vault provider. |
Indeed @jberkenbilt it's not a simple dilema, so I totally understand the pragmatic view. This provider is maintained by Hashicorp, but has been under low maintenance for quite some time. Thus, there are some rough edges caused by debt that, if tackled, can put the codebase in a better direction. The point about 3rd party plugins is also a great one, which I haven't considered.
Due to backward compatibility, there's no easy solution I can suggest to address the previous points, but maybe these can be considered:
|
One more thought in favor of a resource_vault_json_data or similar method, an option we both mentioned: while I appreciate the desire to not have an escape hatch and to instead want a terraform abstraction around vault, the argument for this with vault is not as strong as with other providers, such as AWS. Most providers do not have an open-ended API like vault does. With AWS, GCP, and numerous other kinds of things, there is a fairly set API, and there would be no way to do what you want to do in any way except going through a specific API for that thing. Vault is different by design. With vault, it is specifically designed so that everything can be done through normal read and write operations to API endpoints using a uniform interface. This is actually central to vault's design because the whole way you configure permissions for operations is through ACLs that control access to their endpoints in much the same way they would provide access to secrets. As such, a generic catch-all method for talking to vault is actually supportive of vault's intended design and not "just" a workaround for the terraform vault provider's gaps in implementing a good abstraction. This isn't to say that the gap shouldn't be closed, but I think there's a case to be made that some kind of general get/post mechanism actually makes sense in this provider. |
This is a strong point, totally agree! |
@cvbarros Okay, I'll take what I did, peel out of generic secret, disentangle it from the v1/v2 stuff, etc. and open a different pull request. When that's done, I'll reference it from here and close this one. Thanks for all the engagement on this. I completely agree with your position. |
I like where this is going. A "logical endpoint" Vault resource is sorely needed for encoding configuration outside of Vault itself. |
I'm back at work starting this week and hope to have time to work on this soon. Thanks for all the comments. |
c8980ca
to
dd2af36
Compare
Hi everyone. I've pushed a new version of this up. This commit does not touch
I haven't added any tests yet since I wanted to get a feel for whether this attempt is more on the mark. As I write this, I think I should drop |
I removed Here's a new version of the terraform sample i included originally that works with the new resource.
|
I'm working on tests, and there are a few problems I need to fix. |
2a20d64
to
4a80c8f
Compare
I fixed a minor problem and added an acceptance test, which now passes. This is ready for review. |
I also squashed the commit that removes |
I see there are some conflicts here that I will resolve. Is there a chance we can move on this? I'd like to start using it if we agree on the approach, names, etc. I'll try to find some time in the next few days to resolve the merge conflicts. |
4a80c8f
to
0f90172
Compare
updated to resolve merge conflict |
@cvbarros @daveadams @tyrannosaurus-becks Are any of you able to review or comment on my updated vault generic endpoint resource? Thanks. |
Since changed to the
|
I can add more test cases and update documentation. When done, I will ask for final review. In my doc update, I will describe how to use this resource, update the generic secret resource to recommend use of this resource for generic endpoint access, and also mention in this resource that the generic secret data source is still applicable for just reading arbitrary endpoints. I should be able to get to this early next week. When I'm done, I'll ping again asking for final review and possible merge. Thanks! |
I have pushed up documentation changes. When I update the tests, I will squash that with the original code change commit and indicate that I'm ready for review. If you'd like, you can get a jump on reviewing the doc changes. |
This resource does general writes to vault endpoints. It is preferable to using vault_generic_secret for this purpose for the following reasons: * It allows calling a generic API where a read on the resource returns more or different keys that a write, as is common for many configuration endpoints where only a subset of keys have to be specified * It provides access to data returned by the write operation, such as when using write to create a new resource and needing to retrieve its ID * It enables calling endpoints that can't be deleted
61622bf
to
7a745fa
Compare
@cvbarros (cc @tyrannosaurus-becks) I've updated the docs and updated the tests. The tests were already testing positive and negative Barring any further suggestions or comments, I think it's ready to go. Once merged, how long does it take before a regular terraform init will grab this version? I ask because I have to decide whether to find a way to use a local build in the interim. Thanks! |
Actually, no, it's good. There was a typo in my terraform. :-/ It's working for my use case. Still ready to go. :-) |
@cvbarros Do you know what the next step is for this? |
Hey @jberkenbilt ! I'm just a community member - I provided feedback on your PR in order to help. For me it looks amazing and really helpful, so I'm also keen on having this available 🤞 ! |
@cvbarros Okay, thanks! I'll just sit tight then. I appreciate your review and comments. |
Hi @jberkenbilt! Just as an update, this PR has been stalled because we've been discussing what direction we want to take with the Terraform Vault Provider security-wise. Since this is somewhat of a departure from its code up to this point, it's not as much of a shoo-in to approve and merge as other PRs that have been brought in since this one was opened. This PR is a cleverly flexible approach, and I appreciate you submitting it - it has not been forgotten. |
@tyrannosaurus-becks Thanks for the update. I really appreciate it. Would it help if I share a little more insight as to what we're doing and how this helps? Maybe this will convince you to accept it, or maybe it will help you come up with a better approach. Either way would be great for us. :-) I'm happy to provide as much detail as you want, but very briefly, our approach is to use terraform to manage vault configuration but not secrets. For secrets, vault is authoritative, and our approach to configuration control is more along the lines of good backups and using versioned secrets engines where possible. Terraform is good for some of this, but not all of it. A few more details are below. Our basic approach is to use a stand-alone tool (still to be written) that queries vault to get an inventory of everything that's there and ensures that whatever's there, short of actual secrets themselves are represented in terraform. Examples of things we would control in terraform are policies, roles, auth backends, and secret backends. This reason for this approach is that, in the case vault, the stuff that's outside of configuration control is a great liability, so we want to make sure that everything is under configuration control and drift is quickly detected. Once we have done this, it's easier to ensure that what is under configuration control conforms to our policies. We have developed terraform generator that allows us to make our terraform data-driven. This makes it easier for us to enforce policy and separate the policy from the data in a way that reduces errors. We also use a two-stage authorization method where the first stage is to acquire a token in an environment-specific way (user login, service account login through kubernetes, IAM instance profile, app role, etc.) and then to use that token to get a role-specific token using a create token for a role in the token backend. We want policies attached to stage one tokens to do nothing other than grant access to create stage two tokens. It's much easier to validate and enforce this kind of policy if you can guarantee that all policies are in terraform, and all terraform code that generates policies conforms to certain rules. This approach allows us to use terraform for what it's good at: detecting drift, managing state, and generally synchronizing the state of the system with the code that describes the system. However since of the configuration endpoints have properties that makes code such as in this pull request necessary, or alternatively, we could write specific resources for the small handful of cases we have run into. Right now, we have a lot of terraform code that uses the In spite of having authored this pull request, I also am not sure that it's really the right approach. There are a lot of ways in which terraform is not a great fit for controlling vault configuration, and the risk of persisting sensitive information in terraform state by mistake is pretty high. While this change makes it possible for us to use terraform to handle all the configuration cases were currently have, we may end up rolling our own solution for vault configuration and stepping away from the terraform vault provider entirely, or we may end up using it only for a few basic things, such as configuring policies and roles and mapping users and groups in the various auth backends to the policies that allow them to obtain second stage tokens. So far, there or only a small handful of cases we can't handle with I am happy to provide more details about any of what I mentioned above. I'm planning on blogging about our data-driven terraform approach in the context of a blog I'm planning writing about declarative and imperative systems. I also expect to blog about our two stage authorization approach. Thanks again for the update. I think it's always worth waiting for the right solution, and I would not want a solution to be accepted that would push people in a wrong strategic direction. I have often been in the position of not accepting changes to systems I own for this reason. |
Great reasoning, @jberkenbilt! We also use terraform-vault in almost the same fashion as you do and for the same reasons. Just my 2c |
Hi @jberkenbilt ! Thanks for your patience on this one. I think this is a really good approach. The long pause was because we were discussing whether we'd like to do an approach like this, or maybe start generating resources using OpenAPI, which was recently introduced in Vault, or maybe we'd like to wait until further potential language features arrive in Terraform. After all this, I don't see any reason not to move forward with this PR. Thanks to semantic versioning, if for some reason we decide to go the other way and not use this in the future, we can always deprecate it. Also, as you've pointed out, it does provide an avenue for people to use the Terraform Vault Provider even when a resource is not yet available, and I think that's really cool, especially since the vast majority of this provider's code is currently community-sourced. I also like how you've explicitly noted in the docs that it's best to avoid placing secrets in config or Terraform state. Plus, both the code and the test coverage is superb. The code has developed a conflict in the tremendous amount of time it took to decide and I don't want to burden you with dealing with it. I'm going to move these changes to a native branch so I can add a merge commit easily, but don't worry, you'll still retain credit for your commits. I'll do a bit of testing and comment there if there are any further issues, though I don't anticipate any. Thanks for sticking this one out! |
Closing in favor of #374 . |
NOTE the description here has been superceded by later comments.
I'm opening this pull request as a starting point for discussion. It adds some functionality to vault_generic_secret that makes it much more broadly usable for vault configuration control. I'm interested to see what you think. This is my first time in the code, so every detail of this is just my first stab at it.
In version 1.3.1 of the terraform vault provider, vault_generic_secret
has limitations that make it hard to use for the following two cases:
that a write, as is common for many configuration endpoints where
only a subset of keys have to be specified
information, such as when using write to create a new resource and
needing to retrieve its ID.
This change adds three new fields:
copying the originally supplied data_json and overwriting individual
fields with values returned by the read operation
the write operation, if any
read operation; identical to data_json if ignore_absent_fields is
false; otherwise, contains what the read actually returned
Here is an example of using this. You can start a fresh vault server in dev mode and apply this terraform project against it. This uses to vault_generic_secret to write a subset of fields to
auth/userpass/users/u1
, including an initial dummy password, without caring that the read returns a bunch of other fields and does not include password, and it writesidentity/lookup/entity
and pulls out the resulting lookup information. You can't get this from a vault_generic_secret data source because it's a write operation, not a read operation, that looks up the information.