1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

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 <felix@fontein.de>
This commit is contained in:
anshulbehl 2020-06-30 01:36:08 -04:00 committed by GitHub
parent d2ee51253d
commit cca84abeb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 8 additions and 269 deletions

View file

@ -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)."

View file

@ -57,6 +57,12 @@ action_groups:
- ovirt_vm_facts - ovirt_vm_facts
- ovirt_vmpool_facts - ovirt_vmpool_facts
plugin_routing: 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: modules:
ali_instance_facts: ali_instance_facts:
deprecation: deprecation:

View file

@ -1,159 +0,0 @@
# (c) 2018, Jason Vanderhoof <jason.vanderhoof@cyberark.com>, Oren Ben Meir <oren.benmeir@cyberark.com>
# (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'])

View file

@ -1,110 +0,0 @@
# -*- coding: utf-8 -*-
# (c) 2018, Jason Vanderhoof <jason.vanderhoof@cyberark.com>
#
# 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 <http://www.gnu.org/licenses/>.
# 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')