-
Notifications
You must be signed in to change notification settings - Fork 129
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 support for Google Secret Manager for issue 543 #578
Adding support for Google Secret Manager for issue 543 #578
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.
Thanks for implementing this - it's awesome to see this feature being worked on.
In terms of the code for this lookup, is there a reason of not leveraging ansible_collections.google.cloud.plugins.module_utils.gcp_utils
as a helper for handling the authentication workflow? IMO it would simplify the code and also gain the benefit of being able to use the new OAUTH token as well - recently added by this PR - #574.
I have used it in my private Collection and tested working fine. Code snippet here - you can add the additional env handling, but the heavy lifting of the authentication workflows and API requests are taken care of by gcp_utils
try:
import os
import requests
import json
import base64
except ImportError:
pass
try:
from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import (
GcpSession,
)
HAS_GOOGLE_CLOUD_COLLECTION = True
except ImportError:
HAS_GOOGLE_CLOUD_COLLECTION = False
display = Display()
class GcpMockModule(object):
def __init__(self, params):
self.params = params
def fail_json(self, *args, **kwargs):
raise AnsibleError(kwargs["msg"])
def raise_for_status(self, response):
try:
response.raise_for_status()
except getattr(requests.exceptions, "RequestException"):
self.fail_json(msg="GCP returned error: %s" % response.json())
class GcpSecretLookup:
def run(self, variables=None, **kwargs):
params = {
"project": kwargs.get("project", None),
"secret": kwargs.get("secret", None),
"version": kwargs.get("version", "latest"),
"auth_kind": kwargs.get("auth_kind", None),
"service_account_file": kwargs.get("service_account_file", None),
"service_account_email": kwargs.get("service_account_email", None),
"access_token": kwargs.get("access_token", None), # added for https://github.com/ansible-collections/google.cloud/pull/574
"scopes": kwargs.get("scopes", None),
}
if not params["scopes"]:
params["scopes"] = ["https://www.googleapis.com/auth/cloud-platform"]
fake_module = GcpMockModule(params)
result = self.get_secret(fake_module)
return [base64.b64decode(result)]
def get_secret(self, module):
auth = GcpSession(module, "secretmanager")
url = "https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{secret}/versions/{version}:access".format(
**module.params
)
response = auth.get(url)
return response.json()['payload']['data']
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
if not HAS_GOOGLE_CLOUD_COLLECTION:
raise AnsibleError(
"gcp_secret lookup needs a supported version of the google.cloud collection installed. Use `ansible-galaxy collection install google.cloud` to install it"
)
return GcpSecretLookup().run(terms, variables=variables, **kwargs)
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.
Thank you so much for this suggestion. I'll integrate these changes.
params['name'] = params['key'] | ||
|
||
# support GCP_* env variables for some parameters | ||
for param in ["project", "auth_kind", "service_account_file", "service_account_info", "service_account_email", "access_token"]: |
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.
As you're now using gcp_utils
to handle authentication, this part is no longer needed here. This is because in gcp_utils
there is already mechanism handling the fallback to env.
result = self.get_secret(fake_module) | ||
return [base64.b64decode(result)] | ||
|
||
def fallback_from_env(self, arg): |
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.
As per above - this is already being handled by gcp_utils
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.
Once again, thank you for reviewing my code. Much appreciated.
There is indeed that functionality within the GcpModule class (in the init method for example) here . however, in your example, and the one I committed, we don't actually use that class only GcpSession.
I don't believe I can reuse the GcpModule class here in my lookup plugin. I did actually try this for grins and I got this error when attempting to call my super class constructor:
TypeError: AnsibleModule.__init__() got multiple values for argument 'argument_spec'
Not sure this is possible? What do you think?
And once again, thank you so much.
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.
@dcostakos you're right. Sorry, I got mixed up between the module and lookup helper as I just created a PR for a new module.
Your env handling is absolutely fine.
Thanks for doing this. Hope your PR gets merged soon, and we can all start using the default GCP collection rather than maintaining separate collections.
return merge_dicts(get_obj, access_obj) | ||
|
||
def merge_dicts(x, y): | ||
z = x.copy() |
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.
note that this is only a shallow copy (nested objects will still be modified) - is that ok? on first glance it seems like it's fine, but just checking.
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.
Nice catch, I don't see why it wouldn't be okay to do the shallow copy here but now that you've brought it up, I feel like I should do a deep copy :D.
z.update(y) | ||
return z | ||
|
||
def snake_to_camel(snake): |
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.
unused function? seems like it should be a utility in a separate module anyway.
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, unusued indeed. I forgot to remove this and will do so.
As a note, this module has someone limited parameter support in creating new secrets/secret versions. My thinking here was to get functionality out the door and see what people use. If people need more functionality, it could be added in the future.
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.
Adding feature as-needed seems totally reasonable to me!
thanks for removing the dead code.
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 for the change! overall it looks solid.
A couple questions, if you could get around to them. I know my review is pretty belated.
If I don't hear back I'll try to take a stab at merging this in.
Thank you so much for reviewing this and trying the merge. You caught me at like the worst time on this, but I'm getting back to resolving the issues you brought up and the lint issues with the merge today/tomorrow. Cheers, I'll make an updated commit soon. |
sounds great! and no worries - apologies about my belated review here. Just ping me when you're ready again - I'm trying to be a little more reactive to PR reviews. |
@toumorokoshi Just checking in on this. I'm sure you have a million things to do, but just wanted to check with you to see if this was still something that could potentially be merged. Thank you for your time on this. |
Hey just checking back on this. Again, I'm sure you're very busy. I also have no idea what your release plans are for this. Curious if this will be merged? |
Hi! Unfortunately I've left Google and am no longer the official repository maintainer. That said: it looks like I still have permissions to merge. @SirGitsalot are you able to take a look? or do you want me to see if I can review and get this in? |
it's a real bummer that this got missed for another release. Shoudl we just close this issue if it won't be worked on? |
I'll ping @SirGitsalot again - as much as I'd like to help merge this in since I'm not an employee I think it's best if we leave it to the official maintainer. |
Thanks @toumorokoshi, for helping even though you've moved on. Can you assign @SirGitsalot to be the reviewer of this instead of you? |
done! |
- retrieve secret keys in Secret Manager for use in playbooks | ||
- see https://cloud.google.com/iam/docs/service-account-creds for details on creating | ||
credentials for Google Cloud and the format of such credentials | ||
- once a secret value is retreived, it is returned decoded. It is up to the developer |
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.
- once a secret value is retreived, it is returned decoded. It is up to the developer | |
- once a secret value is retrieved, it is returned decoded. It is up to the developer |
default: 'strict' | ||
scopes: | ||
description: | ||
- Authenticaiton scopes for Google Secret Manager |
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.
- Authenticaiton scopes for Google Secret Manager | |
- Authentication scopes for Google Secret Manager |
fetch['msg'] = "Secret Destroyed, it may take time to propagate" | ||
changed = True | ||
|
||
# check to see if the values are the same, and update if neede |
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.
# check to see if the values are the same, and update if neede | |
# check to see if the values are the same, and update if needed |
It works like a charm, you made my day, thank you 🙇 |
self.raise_error(module, f"unable to list versions of secret {response.status_code}") | ||
version_list = response.json() | ||
if "versions" in version_list: | ||
return sorted(version_list['versions'], key=lambda d: d['name'])[-1]['name'].split('/')[-1] |
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.
It does not return the latest version when you have more than 10. The problem is that it sorts the paths before getting the version, so somepath/12
is smaller than somepath/9
, so it always returns 9
as the latest version.
return sorted(version_list['versions'], key=lambda d: d['name'])[-1]['name'].split('/')[-1] | |
versions_numbers = [] | |
for version in version_list['versions']: | |
versions_numbers.append(version['name'].split('/')[-1]) | |
return sorted(versions_numbers, key=int)[-1] |
return None | ||
|
||
if "versions" in version_list: | ||
latest_version = sorted(version_list['versions'], key=lambda d: d['name'])[-1]['name'].split('/')[-1] |
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.
ditto
latest_version = sorted(version_list['versions'], key=lambda d: d['name'])[-1]['name'].split('/')[-1] | |
versions_numbers = [] | |
for version in version_list['versions']: | |
versions_numbers.append(version['name'].split('/')[-1]) | |
latest_version = sorted(versions_numbers, key=int)[-1]``` |
@SirGitsalot |
I'm not sure if this will ever be merged. I'm the author, I know there's feedback on this, but I'm not open to making more changes to something that will never be merged. Not sure what Google's strategy on this collection is any longer. Feel free to try to manually merge my fork into a collection if you really need it. |
Hi, |
Hi @SirGitsalot, repeating the request from @martiniDB. Any timeline for this module to be released? |
I haven't tried it out yet, but it looks like it got published in https://github.com/ansible-collections/google.cloud/releases/tag/v1.4.0 and docs are on Galaxy here! Thanks @SirGitsalot! |
SUMMARY
Add support for Google Secret Manager as both a module and lookup plugin. Fixes #543
ISSUE TYPE
COMPONENT NAME
gcp_secret_manager
ADDITIONAL INFORMATION
Basic support for GCP secret manager with some testing