mirror of
https://github.com/ansible-collections/google.cloud.git
synced 2025-08-02 04:04:30 -07:00
Adding support for Google Secret Manager for issue 543
This commit is contained in:
parent
30a4e66363
commit
76eb024c25
2 changed files with 626 additions and 0 deletions
213
plugins/lookup/gcp_secret_manager.py
Normal file
213
plugins/lookup/gcp_secret_manager.py
Normal file
|
@ -0,0 +1,213 @@
|
|||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author:
|
||||
- Dave Costakos <dcostako@redhat.com>
|
||||
name: gcp_secret_manager
|
||||
short_description: Get Secrets from Google Cloud as a Lookup plugin
|
||||
description:
|
||||
- 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
|
||||
to maintain secrecy of this value once returned.
|
||||
|
||||
options:
|
||||
key:
|
||||
description:
|
||||
- the key of the secret to look up in Secret Manager
|
||||
type: str
|
||||
required: True
|
||||
project:
|
||||
description:
|
||||
- The name of the google cloud project
|
||||
- defaults to OS env variable GCP_PROJECT if not present
|
||||
type: str
|
||||
auth_kind:
|
||||
description:
|
||||
- the type of authentication to use with Google Cloud (i.e. serviceaccount or machineaccount)
|
||||
- defaults to OS env variable GCP_AUTH_KIND if not present
|
||||
type: str
|
||||
version:
|
||||
description:
|
||||
- the version name of your secret to retrieve
|
||||
type: str
|
||||
default: latest
|
||||
required: False
|
||||
service_account_email:
|
||||
description:
|
||||
- email associated with the service account
|
||||
- defaults to OS env variable GCP_SERVICE_ACCOUNT_EMAIL if not present
|
||||
type: str
|
||||
required: False
|
||||
service_account_file:
|
||||
description:
|
||||
- JSON Credential file obtained from Google Cloud
|
||||
- defaults to OS env variable GCP_SERVICE_ACCOUNT_FILE if not present
|
||||
- see https://cloud.google.com/iam/docs/service-account-creds for details
|
||||
type: str
|
||||
required: False
|
||||
service_account_info:
|
||||
description:
|
||||
- JSON Object representing the contents of a service_account_file obtained from Google Cloud
|
||||
- defaults to OS env variable GCP_SERVICE_ACCOUNT_INFO if not present
|
||||
type: jsonarg
|
||||
required: False
|
||||
errors:
|
||||
description:
|
||||
- how to handle errors
|
||||
choices: ['strict','warn','ignore']
|
||||
default: strict
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Test secret using env variables for credentials
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('google.cloud.gcp_secret_manager', key='secret_key') }}"
|
||||
|
||||
- name: Test secret using explicit credentials
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('google.cloud.gcp_secret_manager', key='secret_key', project='project', auth_kind='serviceaccount', service_account_file='file.json') }}"
|
||||
|
||||
- name: Test getting specific version of a secret (old version)
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('google.cloud.gcp_secret_manager', key='secret_key', version='1') }}"
|
||||
|
||||
- name: Test getting specific version of a secret (new version)
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ lookup('google.cloud.gcp_secret_manager', key='secret_key', version='2') }}"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
_raw:
|
||||
description: the contents of the secret requested (please use "no_log" to not expose this secret)
|
||||
type: list
|
||||
elements: str
|
||||
'''
|
||||
|
||||
################################################################################
|
||||
# Imports
|
||||
################################################################################
|
||||
|
||||
import json
|
||||
import os
|
||||
import base64
|
||||
|
||||
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
try:
|
||||
import requests
|
||||
HAS_REQUESTS = True
|
||||
except ImportError:
|
||||
HAS_REQUESTS = False
|
||||
|
||||
try:
|
||||
import google.auth
|
||||
from google.oauth2 import service_account
|
||||
from google.auth.transport.requests import AuthorizedSession
|
||||
HAS_GOOGLE_LIBRARIES = True
|
||||
except ImportError:
|
||||
HAS_GOOGLE_LIBRARIES = False
|
||||
|
||||
from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import GcpSession, GcpRequest
|
||||
from ansible.errors import AnsibleError
|
||||
|
||||
class GcpLookupException(Exception):
|
||||
pass
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
def run(self, terms, variables, **kwargs):
|
||||
self.set_options(var_options=variables, direct=kwargs)
|
||||
self.scopes = ["https://www.googleapis.com/auth/cloud-platform"]
|
||||
self._validate()
|
||||
self.service_acct_creds = self._credentials()
|
||||
session = AuthorizedSession(self.service_acct_creds)
|
||||
response = session.get("https://secretmanager.googleapis.com/v1/projects/{project}/secrets/{key}/versions/{version}:access".format(**self.get_options()))
|
||||
if response.status_code == 200:
|
||||
result_data = response.json()
|
||||
secret_value = base64.b64decode(result_data['payload']['data'])
|
||||
return [ secret_value ]
|
||||
else:
|
||||
if self.get_option('errors') == 'warn':
|
||||
self.warn(f"secret request returned bad status: {response.status_code} {response.json()}")
|
||||
return [ '' ]
|
||||
elif self.get_option('error') == 'ignore':
|
||||
return [ '' ]
|
||||
else:
|
||||
raise AnsibleError(f"secret request returned bad status: {response.status_code} {response.json()}")
|
||||
|
||||
def _validate(self):
|
||||
if HAS_GOOGLE_LIBRARIES == False:
|
||||
raise AnsibleError("Please install the google-auth library")
|
||||
|
||||
if HAS_REQUESTS == False:
|
||||
raise AnsibleError("Please install the requests library")
|
||||
|
||||
if self.get_option('key') == None:
|
||||
raise AnsibleError("'key' is a required parameter")
|
||||
|
||||
if self.get_option('version') == None:
|
||||
self.set_option('version', 'latest')
|
||||
|
||||
self._set_from_env('project', 'GCP_PROJECT', True)
|
||||
self._set_from_env('auth_kind', 'GCP_AUTH_KIND', True)
|
||||
self._set_from_env('service_account_email', 'GCP_SERVICE_ACCOUNT_EMAIL')
|
||||
self._set_from_env('service_account_file', 'GCP_SERVICE_ACCOUNT_FILE')
|
||||
self._set_from_env('service_account_info', 'GCP_SERVICE_ACCOUNT_INFO')
|
||||
|
||||
def _set_from_env(self, var=None, env_name=None, raise_on_empty=False):
|
||||
if self.get_option(var) == None:
|
||||
if env_name is not None and env_name in os.environ:
|
||||
fallback = os.environ[env_name]
|
||||
self.set_option(var, fallback)
|
||||
|
||||
if self.get_option(var) == None and raise_on_empty:
|
||||
msg = f"No key '{var}' provided"
|
||||
if env_name is not None:
|
||||
msg += f" and no fallback to env['{env_name}'] available"
|
||||
raise AnsibleError(msg)
|
||||
|
||||
def _credentials(self):
|
||||
cred_type = self.get_option('auth_kind')
|
||||
|
||||
if cred_type == 'application':
|
||||
credentials, project_id = google.auth.default(scopes=self.scopes)
|
||||
return credentials
|
||||
|
||||
if cred_type == 'serviceaccount':
|
||||
if self.get_option('service_account_file') is not None:
|
||||
path = os.path.realpath(os.path.expanduser(self.get_option('service_account_file')))
|
||||
try:
|
||||
svc_acct_creds = service_account.Credentials.from_service_account_file(path)
|
||||
except OSError as e:
|
||||
raise GcpLookupException("Unable to read service_account_file at %s: %s" % (path, e.strerror))
|
||||
|
||||
elif self.get_option('service_account_contents') is not None:
|
||||
try:
|
||||
info = json.loads(self.get_option('service_account_contents'))
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise GcpLookupException("Unable to decode service_account_contents as JSON: %s" % e)
|
||||
|
||||
svc_acct_creds = service_account.Credentials.from_service_account_info(info)
|
||||
else:
|
||||
raise GcpLookupException('Service Account authentication requires setting either service_account_file or service_account_contents')
|
||||
|
||||
return svc_acct_creds.with_scopes(self.scopes)
|
||||
|
||||
if cred_type == 'machineaccount':
|
||||
self.svc_acct_creds = google.auth.compute_engine.Credentials(self.service_account_email)
|
||||
return self.svc_acct_creds
|
||||
|
||||
raise GcpLookupException("Credential type '%s' not implemented" % cred_type)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue