mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
160 lines
6 KiB
Python
160 lines
6 KiB
Python
|
# (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'])
|