-
Notifications
You must be signed in to change notification settings - Fork 130
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: CCP secret manager lookup(https://github.com/ansible-collections/google.cloud/pull/357) #628
base: master
Are you sure you want to change the base?
Changes from all commits
2b74f48
2679d72
d46df5e
21a84df
6a7723a
35e0364
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,104 @@ | ||||||||
# -*- coding: utf-8 -*- | ||||||||
# Copyright: (c) 2020, Pavlo Bashynskyi (@levonet) <[email protected]> | ||||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||||||||
from __future__ import (absolute_import, division, print_function) | ||||||||
__metaclass__ = type | ||||||||
|
||||||||
DOCUMENTATION = r""" | ||||||||
--- | ||||||||
lookup: gcp_secret_access | ||||||||
author: | ||||||||
- Pavlo Bashynskyi (@levonet) | ||||||||
short_description: Retrieve secrets from GCP Secret Manager | ||||||||
requirements: | ||||||||
- python >= 2.7 | ||||||||
- google-auth >= 1.26.0 | ||||||||
- google-cloud-secret-manager >= 1.0.0 | ||||||||
description: | ||||||||
- Retrieve secret contents from GCP Secret Manager. | ||||||||
- Accessing to secret content requires the Secret Manager Secret Accessor role (C(roles/secretmanager.secretAccessor)) on the secret, project, folder, or organization. | ||||||||
options: | ||||||||
secret: | ||||||||
description: | ||||||||
- Secret name or resource id. Resource id should be in format C(projects/*/secrets/*/versions/*). | ||||||||
- The project option is required if a secret name is used instead of resource id. | ||||||||
required: True | ||||||||
type: str | ||||||||
version: | ||||||||
description: Version id of secret. You can also access the latest version of a secret by specifying "C(latest)" as the version. | ||||||||
type: str | ||||||||
default: latest | ||||||||
project: | ||||||||
description: The Google Cloud Platform project to use. | ||||||||
type: str | ||||||||
env: | ||||||||
- name: GCP_PROJECT | ||||||||
access_token: | ||||||||
description: | ||||||||
- The Google Cloud access token. If specified, C(service_account_file) will be ignored. | ||||||||
type: str | ||||||||
env: | ||||||||
- name: GCP_ACCESS_TOKEN | ||||||||
service_account_file: | ||||||||
description: | ||||||||
- The path of a Service Account JSON file if serviceaccount is selected as type. | ||||||||
type: path | ||||||||
env: | ||||||||
- name: GOOGLE_APPLICATION_CREDENTIALS | ||||||||
- name: GCP_SERVICE_ACCOUNT_FILE | ||||||||
notes: | ||||||||
- When I(secret) is the first option in the term string, C(secret=) is not required (see examples). | ||||||||
- If you’re running your application elsewhere, you should download a service account JSON keyfile and point to it using the secret option or an environment variable C(GOOGLE_APPLICATION_CREDENTIALS="/path/to/keyfile.json"). | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
""" | ||||||||
|
||||||||
EXAMPLES = r""" | ||||||||
- ansible.builtin.debug: | ||||||||
msg: "{{ lookup('google.cloud.gcp_secret_access', secret='hola', project='test_project') }}" | ||||||||
|
||||||||
- ansible.builtin.debug: | ||||||||
msg: "{{ lookup('google.cloud.gcp_secret_access', 'hola', project='test_project') }}" | ||||||||
|
||||||||
- name: using resource id instead of secret name | ||||||||
ansible.builtin.debug: | ||||||||
msg: "{{ lookup('google.cloud.gcp_secret_access', 'projects/112233445566/secrets/hola/versions/1') }}" | ||||||||
|
||||||||
- name: using service account file | ||||||||
ansible.builtin.debug: | ||||||||
msg: "{{ lookup('google.cloud.gcp_secret_access', 'hola', project='test_project', service_account_file='/path/to/keyfile.json') }}" | ||||||||
""" | ||||||||
|
||||||||
RETURN = r""" | ||||||||
_raw: | ||||||||
description: | ||||||||
- secrets requested | ||||||||
""" | ||||||||
|
||||||||
from ansible.errors import AnsibleError | ||||||||
from ansible.plugins.lookup import LookupBase | ||||||||
from ansible_collections.google.cloud.plugins.plugin_utils.gcp_utils import GcpSecretLookup | ||||||||
|
||||||||
try: | ||||||||
from google.cloud import secretmanager | ||||||||
|
||||||||
HAS_GOOGLE_SECRET_MANAGER_LIBRARY = True | ||||||||
except ImportError: | ||||||||
HAS_GOOGLE_SECRET_MANAGER_LIBRARY = False | ||||||||
|
||||||||
|
||||||||
class GcpSecretAccessLookup(GcpSecretLookup): | ||||||||
def run(self, terms, variables=None, **kwargs): | ||||||||
self.set_plugin_name('google.cloud.gcp_secret_access') | ||||||||
self.process_options(terms, variables=None, **kwargs) | ||||||||
|
||||||||
response = self.client(secretmanager).access_secret_version(request={"name": self.name}) | ||||||||
payload = response.payload.data.decode("UTF-8") | ||||||||
|
||||||||
return [payload] | ||||||||
|
||||||||
|
||||||||
class LookupModule(LookupBase): | ||||||||
def run(self, terms, variables=None, **kwargs): | ||||||||
if not HAS_GOOGLE_SECRET_MANAGER_LIBRARY: | ||||||||
raise AnsibleError("Please install the google-cloud-secret-manager Python library") | ||||||||
|
||||||||
return GcpSecretAccessLookup().run(terms, variables=variables, **kwargs) |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,92 @@ | ||||||||
# -*- coding: utf-8 -*- | ||||||||
# Copyright: (c) 2020, Pavlo Bashynskyi (@levonet) <[email protected]> | ||||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||||||||
from __future__ import (absolute_import, division, print_function) | ||||||||
__metaclass__ = type | ||||||||
|
||||||||
DOCUMENTATION = r""" | ||||||||
--- | ||||||||
lookup: gcp_secret_resource_id | ||||||||
author: | ||||||||
- Pavlo Bashynskyi (@levonet) | ||||||||
short_description: Retrieve resource id of secret version from GCP Secret Manager | ||||||||
requirements: | ||||||||
- python >= 2.7 | ||||||||
- google-auth >= 1.26.0 | ||||||||
- google-cloud-secret-manager >= 1.0.0 | ||||||||
description: | ||||||||
- Retrieve resource id of secret version from GCP Secret Manager. | ||||||||
options: | ||||||||
secret: | ||||||||
description: | ||||||||
- Secret name or resource id. Resource id should be in format C(projects/*/secrets/*/versions/*). | ||||||||
- The project option is required if a secret name is used instead of resource id. | ||||||||
required: True | ||||||||
type: str | ||||||||
version: | ||||||||
description: Version id of secret. You can also access the latest version of a secret by specifying "C(latest)" as the version. | ||||||||
type: str | ||||||||
default: latest | ||||||||
project: | ||||||||
description: The Google Cloud Platform project to use. | ||||||||
type: str | ||||||||
env: | ||||||||
- name: GCP_PROJECT | ||||||||
access_token: | ||||||||
description: | ||||||||
- The Google Cloud access token. If specified, C(service_account_file) will be ignored. | ||||||||
type: str | ||||||||
env: | ||||||||
- name: GCP_ACCESS_TOKEN | ||||||||
service_account_file: | ||||||||
description: | ||||||||
- The path of a Service Account JSON file if serviceaccount is selected as type. | ||||||||
type: path | ||||||||
env: | ||||||||
- name: GOOGLE_APPLICATION_CREDENTIALS | ||||||||
- name: GCP_SERVICE_ACCOUNT_FILE | ||||||||
notes: | ||||||||
- When I(secret) is the first option in the term string, C(secret=) is not required (see examples). | ||||||||
- If you’re running your application elsewhere, you should download a service account JSON keyfile and point to it using the secret option or an environment variable C(GOOGLE_APPLICATION_CREDENTIALS="/path/to/keyfile.json"). | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
""" | ||||||||
|
||||||||
EXAMPLES = r""" | ||||||||
- ansible.builtin.debug: | ||||||||
msg: "{{ lookup('google.cloud.gcp_secret_resource_id', secret='hola', project='test_project') }}" | ||||||||
""" | ||||||||
|
||||||||
RETURN = r""" | ||||||||
_raw: | ||||||||
description: | ||||||||
- resource id of secret version | ||||||||
""" | ||||||||
|
||||||||
from ansible.errors import AnsibleError | ||||||||
from ansible.plugins.lookup import LookupBase | ||||||||
from ansible_collections.google.cloud.plugins.plugin_utils.gcp_utils import GcpSecretLookup | ||||||||
|
||||||||
try: | ||||||||
from google.cloud import secretmanager | ||||||||
|
||||||||
HAS_GOOGLE_SECRET_MANAGER_LIBRARY = True | ||||||||
except ImportError: | ||||||||
HAS_GOOGLE_SECRET_MANAGER_LIBRARY = False | ||||||||
|
||||||||
|
||||||||
class GcpSecretResourceIdLookup(GcpSecretLookup): | ||||||||
def run(self, terms, variables=None, **kwargs): | ||||||||
self.set_plugin_name('google.cloud.gcp_secret_resource_id') | ||||||||
self.process_options(terms, variables=None, **kwargs) | ||||||||
|
||||||||
response = self.client(secretmanager).get_secret_version(request={"name": self.name}) | ||||||||
|
||||||||
return [response.name] | ||||||||
|
||||||||
|
||||||||
class LookupModule(LookupBase): | ||||||||
def run(self, terms, variables=None, **kwargs): | ||||||||
|
||||||||
if not HAS_GOOGLE_SECRET_MANAGER_LIBRARY: | ||||||||
raise AnsibleError("Please install the google-cloud-secret-manager Python library") | ||||||||
|
||||||||
return GcpSecretResourceIdLookup().run(terms, variables=variables, **kwargs) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,105 @@ | ||||||
# -*- coding: utf-8 -*- | ||||||
# Copyright: (c) 2020, Pavlo Bashynskyi (@levonet) <[email protected]> | ||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||||||
from __future__ import (absolute_import, division, print_function) | ||||||
|
||||||
__metaclass__ = type | ||||||
|
||||||
import io | ||||||
import json | ||||||
import os | ||||||
import re | ||||||
|
||||||
try: | ||||||
import google.oauth2.credentials | ||||||
from google.auth import identity_pool | ||||||
from google.oauth2 import service_account | ||||||
HAS_GOOGLE_LIBRARIES = True | ||||||
except ImportError: | ||||||
HAS_GOOGLE_LIBRARIES = False | ||||||
|
||||||
from ansible.errors import AnsibleError | ||||||
|
||||||
|
||||||
# Handles all authentication and options for GCP Secrets Manager API calls in Lookup plugins. | ||||||
class GcpSecretLookup(): | ||||||
def __init__(self): | ||||||
if not HAS_GOOGLE_LIBRARIES: | ||||||
raise AnsibleError("Please install the google-auth library") | ||||||
|
||||||
self.plugin_name = '' | ||||||
self.secret_id = None | ||||||
self.version_id = None | ||||||
self.project_id = None | ||||||
self.access_token = None | ||||||
self.service_account_file = None | ||||||
self.scope = ["https://www.googleapis.com/auth/cloud-platform"] | ||||||
|
||||||
def set_plugin_name(self, name): | ||||||
self.plugin_name = name | ||||||
|
||||||
def client(self, secretmanager): | ||||||
if self.access_token is not None: | ||||||
credentials=google.oauth2.credentials.Credentials(self.access_token, scopes=self.scope) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
return secretmanager.SecretManagerServiceClient(credentials=credentials) | ||||||
|
||||||
if self.service_account_file is not None: | ||||||
path = os.path.realpath(os.path.expanduser(self.service_account_file)) | ||||||
if not os.path.exists(path): | ||||||
raise AnsibleError("File {} was not found.".format(path)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
with io.open(path, "r") as file_obj: | ||||||
try: | ||||||
info = json.load(file_obj) | ||||||
except ValueError as e: | ||||||
raise AnsibleError("File {} is not a valid json file.".format(path)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
credential_type = info.get("type") | ||||||
if credential_type == "authorized_user": | ||||||
credentials = google.oauth2.credentials.Credentials.from_authorized_user_info(info, scopes=self.scope) | ||||||
elif credential_type == "service_account": | ||||||
credentials = service_account.Credentials.from_service_account_info(info, scopes=self.scope) | ||||||
elif credential_type == "external_account": | ||||||
if info.get("subject_token_type") == "urn:ietf:params:aws:token-type:aws4_request": | ||||||
from google.auth import aws | ||||||
credentials = aws.Credentials.from_info(info, scopes=self.scope) | ||||||
else: | ||||||
credentials = identity_pool.Credentials.from_info(info, scopes=self.scope) | ||||||
else: | ||||||
raise AnsibleError( | ||||||
"Type is {}, expected one of authorized_user, service_account, external_account.".format(credential_type) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
) | ||||||
|
||||||
return secretmanager.SecretManagerServiceClient(credentials=credentials) | ||||||
|
||||||
return secretmanager.SecretManagerServiceClient() | ||||||
|
||||||
def process_options(self, terms, variables=None, **kwargs): | ||||||
self.secret_id = kwargs.get('secret') | ||||||
self.version_id = kwargs.get('version', 'latest') | ||||||
self.project_id = kwargs.get('project', os.getenv('GCP_PROJECT')) | ||||||
self.access_token = kwargs.get('access_token', os.getenv('GCP_ACCESS_TOKEN')) | ||||||
self.service_account_file = kwargs.get('service_account_file', os.getenv('GOOGLE_APPLICATION_CREDENTIALS')) | ||||||
|
||||||
if len(terms) > 1: | ||||||
raise AnsibleError("{0} lookup plugin can have only one secret name or resource id".format(self.plugin_name)) | ||||||
|
||||||
if self.secret_id is None and len(terms) == 1: | ||||||
self.secret_id = terms[0] | ||||||
|
||||||
regex = r'^projects/([^/]+)/secrets/([^/]+)/versions/(.+)$' | ||||||
match = re.match(regex, self.secret_id) | ||||||
if match: | ||||||
self.name = self.secret_id | ||||||
self.project_id = match.group(1) | ||||||
self.secret_id = match.group(2) | ||||||
self.version_id = match.group(3) | ||||||
return | ||||||
|
||||||
if self.project_id is None: | ||||||
raise AnsibleError("{0} lookup plugin required option: project or resource id".format(self.plugin_name)) | ||||||
|
||||||
if self.secret_id is None: | ||||||
raise AnsibleError("{0} lookup plugin required option: secret or resource id".format(self.plugin_name)) | ||||||
|
||||||
self.name = "projects/{}/secrets/{}/versions/{}".format(self.project_id, self.secret_id, self.version_id) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
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.