From cca84abeb57da17cfc7c321d25c8af0a793f6cb6 Mon Sep 17 00:00:00 2001 From: anshulbehl Date: Tue, 30 Jun 2020 01:36:08 -0400 Subject: [PATCH] conjur_variable: redirecting to correct collection (#570) * - Redirecting to correct collection - Removing the plugin and adding changelog and deprecation * Making suggested changes * Earlier version on leftovers * Update changelogs/fragments/cyberarkconjur-removal.yml Co-authored-by: Felix Fontein --- .../fragments/cyberarkconjur-removal.yml | 2 + meta/runtime.yml | 6 + plugins/lookup/conjur_variable.py | 159 ------------------ .../plugins/lookup/test_conjur_variable.py | 110 ------------ 4 files changed, 8 insertions(+), 269 deletions(-) create mode 100644 changelogs/fragments/cyberarkconjur-removal.yml delete mode 100644 plugins/lookup/conjur_variable.py delete mode 100644 tests/unit/plugins/lookup/test_conjur_variable.py diff --git a/changelogs/fragments/cyberarkconjur-removal.yml b/changelogs/fragments/cyberarkconjur-removal.yml new file mode 100644 index 0000000000..1c3cc6dadf --- /dev/null +++ b/changelogs/fragments/cyberarkconjur-removal.yml @@ -0,0 +1,2 @@ +removed_features: + - "conjur_variable lookup - has been moved to the ``cyberark.conjur`` collection. A redirection is active, which will be removed in version 2.0.0 (https://github.com/ansible-collections/community.general/pull/570)." diff --git a/meta/runtime.yml b/meta/runtime.yml index 5459f1fe05..c578508d7f 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -57,6 +57,12 @@ action_groups: - ovirt_vm_facts - ovirt_vmpool_facts plugin_routing: + lookup: + conjur_variable: + redirect: cyberark.conjur.conjur_variable + deprecation: + removal_version: 2.0.0 + warning_text: The conjur_variable lookup has been moved to the cyberark.conjur collection. modules: ali_instance_facts: deprecation: diff --git a/plugins/lookup/conjur_variable.py b/plugins/lookup/conjur_variable.py deleted file mode 100644 index fcc7a45e45..0000000000 --- a/plugins/lookup/conjur_variable.py +++ /dev/null @@ -1,159 +0,0 @@ -# (c) 2018, Jason Vanderhoof , Oren Ben Meir -# (c) 2018 Ansible Project -# 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 = ''' - lookup: conjur_variable - short_description: Fetch credentials from CyberArk Conjur. - description: - - "Retrieves credentials from Conjur using the controlling host's Conjur identity. Conjur info: U(https://www.conjur.org/)." - requirements: - - 'The controlling host running Ansible has a Conjur identity. - (More: U(https://docs.conjur.org/Latest/en/Content/Get%20Started/key_concepts/machine_identity.html))' - options: - _term: - description: Variable path - required: True - identity_file: - description: Path to the Conjur identity file. The identity file follows the netrc file format convention. - type: path - default: /etc/conjur.identity - required: False - ini: - - section: conjur, - key: identity_file_path - env: - - name: CONJUR_IDENTITY_FILE - config_file: - description: Path to the Conjur configuration file. The configuration file is a YAML file. - type: path - default: /etc/conjur.conf - required: False - ini: - - section: conjur, - key: config_file_path - env: - - name: CONJUR_CONFIG_FILE -''' - -EXAMPLES = """ - - debug: - msg: "{{ lookup('conjur_variable', '/path/to/secret') }}" -""" - -RETURN = """ - _raw: - description: - - Value stored in Conjur. -""" - -import os.path -from ansible.errors import AnsibleError -from ansible.plugins.lookup import LookupBase -from base64 import b64encode -from netrc import netrc -from os import environ -from time import time -from ansible.module_utils.six.moves.urllib.parse import quote_plus -import yaml - -from ansible.module_utils.urls import open_url -from ansible.utils.display import Display - -display = Display() - - -# Load configuration and return as dictionary if file is present on file system -def _load_conf_from_file(conf_path): - display.vvv('conf file: {0}'.format(conf_path)) - - if not os.path.exists(conf_path): - raise AnsibleError('Conjur configuration file `{0}` was not found on the controlling host' - .format(conf_path)) - - display.vvvv('Loading configuration from: {0}'.format(conf_path)) - with open(conf_path) as f: - config = yaml.safe_load(f.read()) - if 'account' not in config or 'appliance_url' not in config: - raise AnsibleError('{0} on the controlling host must contain an `account` and `appliance_url` entry' - .format(conf_path)) - return config - - -# Load identity and return as dictionary if file is present on file system -def _load_identity_from_file(identity_path, appliance_url): - display.vvvv('identity file: {0}'.format(identity_path)) - - if not os.path.exists(identity_path): - raise AnsibleError('Conjur identity file `{0}` was not found on the controlling host' - .format(identity_path)) - - display.vvvv('Loading identity from: {0} for {1}'.format(identity_path, appliance_url)) - - conjur_authn_url = '{0}/authn'.format(appliance_url) - identity = netrc(identity_path) - - if identity.authenticators(conjur_authn_url) is None: - raise AnsibleError('The netrc file on the controlling host does not contain an entry for: {0}' - .format(conjur_authn_url)) - - id, account, api_key = identity.authenticators(conjur_authn_url) - if not id or not api_key: - raise AnsibleError('{0} on the controlling host must contain a `login` and `password` entry for {1}' - .format(identity_path, appliance_url)) - - return {'id': id, 'api_key': api_key} - - -# Use credentials to retrieve temporary authorization token -def _fetch_conjur_token(conjur_url, account, username, api_key): - conjur_url = '{0}/authn/{1}/{2}/authenticate'.format(conjur_url, account, username) - display.vvvv('Authentication request to Conjur at: {0}, with user: {1}'.format(conjur_url, username)) - - response = open_url(conjur_url, data=api_key, method='POST') - code = response.getcode() - if code != 200: - raise AnsibleError('Failed to authenticate as \'{0}\' (got {1} response)' - .format(username, code)) - - return response.read() - - -# Retrieve Conjur variable using the temporary token -def _fetch_conjur_variable(conjur_variable, token, conjur_url, account): - token = b64encode(token) - headers = {'Authorization': 'Token token="{0}"'.format(token)} - display.vvvv('Header: {0}'.format(headers)) - - url = '{0}/secrets/{1}/variable/{2}'.format(conjur_url, account, quote_plus(conjur_variable)) - display.vvvv('Conjur Variable URL: {0}'.format(url)) - - response = open_url(url, headers=headers, method='GET') - - if response.getcode() == 200: - display.vvvv('Conjur variable {0} was successfully retrieved'.format(conjur_variable)) - return [response.read()] - if response.getcode() == 401: - raise AnsibleError('Conjur request has invalid authorization credentials') - if response.getcode() == 403: - raise AnsibleError('The controlling host\'s Conjur identity does not have authorization to retrieve {0}' - .format(conjur_variable)) - if response.getcode() == 404: - raise AnsibleError('The variable {0} does not exist'.format(conjur_variable)) - - return {} - - -class LookupModule(LookupBase): - - def run(self, terms, variables=None, **kwargs): - conf_file = self.get_option('config_file') - conf = _load_conf_from_file(conf_file) - - identity_file = self.get_option('identity_file') - identity = _load_identity_from_file(identity_file, conf['appliance_url']) - - token = _fetch_conjur_token(conf['appliance_url'], conf['account'], identity['id'], identity['api_key']) - return _fetch_conjur_variable(terms[0], token, conf['appliance_url'], conf['account']) diff --git a/tests/unit/plugins/lookup/test_conjur_variable.py b/tests/unit/plugins/lookup/test_conjur_variable.py deleted file mode 100644 index 7d2dcb6149..0000000000 --- a/tests/unit/plugins/lookup/test_conjur_variable.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8 -*- -# (c) 2018, Jason Vanderhoof -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -# Make coding more python3-ish -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -import pytest -from ansible_collections.community.general.tests.unit.compat.mock import MagicMock -from ansible.errors import AnsibleError -from ansible.module_utils.six.moves import http_client -from ansible_collections.community.general.plugins.lookup import conjur_variable -import tempfile - - -class TestLookupModule: - def test_valid_netrc_file(self): - with tempfile.NamedTemporaryFile() as temp_netrc: - temp_netrc.write(b"machine http://localhost/authn\n") - temp_netrc.write(b" login admin\n") - temp_netrc.write(b" password my-pass\n") - temp_netrc.seek(0) - - results = conjur_variable._load_identity_from_file(temp_netrc.name, 'http://localhost') - - assert results['id'] == 'admin' - assert results['api_key'] == 'my-pass' - - def test_netrc_without_host_file(self): - with tempfile.NamedTemporaryFile() as temp_netrc: - temp_netrc.write(b"machine http://localhost/authn\n") - temp_netrc.write(b" login admin\n") - temp_netrc.write(b" password my-pass\n") - temp_netrc.seek(0) - - with pytest.raises(AnsibleError): - conjur_variable._load_identity_from_file(temp_netrc.name, 'http://foo') - - def test_valid_configuration(self): - with tempfile.NamedTemporaryFile() as configuration_file: - configuration_file.write(b"---\n") - configuration_file.write(b"account: demo-policy\n") - configuration_file.write(b"plugins: []\n") - configuration_file.write(b"appliance_url: http://localhost:8080\n") - configuration_file.seek(0) - - results = conjur_variable._load_conf_from_file(configuration_file.name) - assert results['account'] == 'demo-policy' - assert results['appliance_url'] == 'http://localhost:8080' - - def test_valid_token_retrieval(self, mocker): - mock_response = MagicMock(spec_set=http_client.HTTPResponse) - try: - mock_response.getcode.return_value = 200 - except Exception: - # HTTPResponse is a Python 3 only feature. This uses a generic mock for python 2.6 - mock_response = MagicMock() - mock_response.getcode.return_value = 200 - - mock_response.read.return_value = 'foo-bar-token' - mocker.patch.object(conjur_variable, 'open_url', return_value=mock_response) - - response = conjur_variable._fetch_conjur_token('http://conjur', 'account', 'username', 'api_key') - assert response == 'foo-bar-token' - - def test_valid_fetch_conjur_variable(self, mocker): - mock_response = MagicMock(spec_set=http_client.HTTPResponse) - try: - mock_response.getcode.return_value = 200 - except Exception: - # HTTPResponse is a Python 3 only feature. This uses a generic mock for python 2.6 - mock_response = MagicMock() - mock_response.getcode.return_value = 200 - - mock_response.read.return_value = 'foo-bar' - mocker.patch.object(conjur_variable, 'open_url', return_value=mock_response) - - response = conjur_variable._fetch_conjur_token('super-secret', 'token', 'http://conjur', 'account') - assert response == 'foo-bar' - - def test_invalid_fetch_conjur_variable(self, mocker): - for code in [401, 403, 404]: - mock_response = MagicMock(spec_set=http_client.HTTPResponse) - try: - mock_response.getcode.return_value = code - except Exception: - # HTTPResponse is a Python 3 only feature. This uses a generic mock for python 2.6 - mock_response = MagicMock() - mock_response.getcode.return_value = code - - mocker.patch.object(conjur_variable, 'open_url', return_value=mock_response) - - with pytest.raises(AnsibleError): - response = conjur_variable._fetch_conjur_token('super-secret', 'token', 'http://conjur', 'account')