diff --git a/plugins/doc_fragments/gcp.py b/plugins/doc_fragments/gcp.py index 5dfeb00e6..76aa00a40 100644 --- a/plugins/doc_fragments/gcp.py +++ b/plugins/doc_fragments/gcp.py @@ -21,7 +21,7 @@ class ModuleDocFragment(object): - The type of credential used. type: str required: true - choices: [ application, machineaccount, serviceaccount ] + choices: [ application, machineaccount, serviceaccount, impersonation ] service_account_contents: description: - The contents of a Service Account JSON file, either in a dictionary or as a JSON string that represents it. @@ -33,7 +33,8 @@ class ModuleDocFragment(object): service_account_email: description: - An optional service account email address if machineaccount is selected - and the user does not wish to use the default email. + and the user does not wish to use the default email. This field must be set + if auth_kind is impersonation. type: str scopes: description: diff --git a/plugins/filter/gcp_kms_filters.py b/plugins/filter/gcp_kms_filters.py index 9be0be0df..4b41d34bb 100644 --- a/plugins/filter/gcp_kms_filters.py +++ b/plugins/filter/gcp_kms_filters.py @@ -3,11 +3,9 @@ # Usage: # vars: -# encrypted_myvar: "{{ var | b64encode | gcp_kms_encrypt(auth_kind='serviceaccount', -# service_account_file='gcp_service_account_file', projects='default', +# encrypted_myvar: "{{ var | b64encode | gcp_kms_encrypt(projects='default', # key_ring='key_ring', crypto_key='crypto_key') }}" -# decrypted_myvar: "{{ encrypted_myvar | gcp_kms_decrypt(auth_kind='serviceaccount', -# service_account_file=gcp_service_account_file, projects='default', +# decrypted_myvar: "{{ encrypted_myvar | gcp_kms_decrypt(projects='default', # key_ring='key_ring', crypto_key='crypto_key') }}" from __future__ import (absolute_import, division, print_function) @@ -36,12 +34,12 @@ def run(self, method, **kwargs): 'projects': kwargs.get('projects', None), 'scopes': kwargs.get('scopes', None), 'locations': kwargs.get('locations', 'global'), - 'auth_kind': kwargs.get('auth_kind', None), + 'auth_kind': kwargs.get('auth_kind', 'application'), 'service_account_file': kwargs.get('service_account_file', None), 'service_account_email': kwargs.get('service_account_email', None), } if not params['scopes']: - params['scopes'] = ['https://www.googleapis.com/auth/cloudkms'] + params['scopes'] = ['https://www.googleapis.com/auth/cloud-platform'] fake_module = GcpMockModule(params) if method == "encrypt": return self.kms_encrypt(fake_module) diff --git a/plugins/filter/gcp_secret_filters.py b/plugins/filter/gcp_secret_filters.py new file mode 100644 index 000000000..80c5c5d50 --- /dev/null +++ b/plugins/filter/gcp_secret_filters.py @@ -0,0 +1,60 @@ +# (c) 2019, Eric Anderson +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Usage: +# vars: +# encrypted_myvar: "{{ var | b64encode | gcp_kms_encrypt(projects='default', +# key_ring='key_ring', crypto_key='crypto_key') }}" +# decrypted_myvar: "{{ encrypted_myvar | gcp_kms_decrypt(projects='default', +# key_ring='key_ring', crypto_key='crypto_key') }}" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.errors import AnsibleError +from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import GcpSession + + +class GcpMockModule(object): + def __init__(self, params): + self.params = params + + def fail_json(self, *args, **kwargs): + raise AnsibleError(kwargs['msg']) + + +class GcpSecretFilter(): + def run(self, method, **kwargs): + params = { + 'secret': kwargs.get('secret', None), + 'versions': kwargs.get('version', 'latest'), + 'projects': kwargs.get('projects', None), + 'scopes': kwargs.get('scopes', None), + 'auth_kind': kwargs.get('auth_kind', 'application'), + 'service_account_file': kwargs.get('service_account_file', None), + 'service_account_email': kwargs.get('service_account_email', None), + } + if not params['scopes']: + params['scopes'] = ['https://www.googleapis.com/auth/cloud-platform'] + fake_module = GcpMockModule(params) + if method == "decode": + return self.secret_decode(fake_module) + + def secret_decode(self, module): + payload = {"ciphertext": module.params['ciphertext']} + + auth = GcpSession(module, 'secretmanager') + url = "https://secretmanager.googleapis.com/v1/projects/{projects}/secrets/{secret}/" \ + "versions/{version}:access".format(**module.params) + response = auth.get(url, body=payload) + return response.json()['payload']['data'] + +def gcp_secret_decode(plaintext, **kwargs): + return GcpKmsFilter().run('decode', plaintext=plaintext, **kwargs) + +class FilterModule(object): + + def filters(self): + return { + 'gcp_secret_decode': gcp_secret_decode, + } diff --git a/plugins/inventory/gcp_compute.py b/plugins/inventory/gcp_compute.py index 65532addb..e83aeadba 100644 --- a/plugins/inventory/gcp_compute.py +++ b/plugins/inventory/gcp_compute.py @@ -52,7 +52,7 @@ description: - The type of credential used. required: True - choices: ['application', 'serviceaccount', 'machineaccount'] + choices: ['application', 'serviceaccount', 'machineaccount', 'impersonation'] env: - name: GCP_AUTH_KIND version_added: "2.8.2" @@ -83,7 +83,8 @@ service_account_email: description: - An optional service account email address if machineaccount is selected - and the user does not wish to use the default email. + and the user does not wish to use the default email. This field must be set + if auth_kind is impersonation. env: - name: GCP_SERVICE_ACCOUNT_EMAIL version_added: "2.8.2" diff --git a/plugins/module_utils/gcp_utils.py b/plugins/module_utils/gcp_utils.py index 643c8ca58..3a9a509a3 100644 --- a/plugins/module_utils/gcp_utils.py +++ b/plugins/module_utils/gcp_utils.py @@ -235,6 +235,14 @@ def _credentials(self): elif cred_type == 'machineaccount': return google.auth.compute_engine.Credentials( self.module.params['service_account_email']) + elif cred_type == 'impersonation' and self.module.params['service_account_email']: + source_credentials, project_id = google.auth.default() + return google.auth.impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal=self.module.params['service_account_email'], + target_scopes=self.module.params['scopes'], + lifetime=3600, + ) else: self.module.fail_json(msg="Credential type '%s' not implemented" % cred_type) @@ -270,7 +278,7 @@ def __init__(self, *args, **kwargs): auth_kind=dict( required=True, fallback=(env_fallback, ['GCP_AUTH_KIND']), - choices=['machineaccount', 'serviceaccount', 'application'], + choices=['machineaccount', 'serviceaccount', 'application', 'impersonation'], type='str'), service_account_email=dict( required=False,