ACI Private_Key String to Allow for Vaulting (#54251)
* Allows the use of Private_Keys to be entered as a string instead of just a file. Making it possible to use VAULT to encrypt the key * Fixed Issues auto check found * Provide helpful information while avoiding credential exposure * Restore original variable name :-) * Fix a few other things * Influence the default certificate_name in both cases * Update documentation * Add contributed docs * Fix CI issue
This commit is contained in:
parent
f9876f3450
commit
62d3ed0e2f
4 changed files with 112 additions and 23 deletions
|
@ -204,10 +204,14 @@ Every Ansible ACI module accepts the following parameters that influence the mod
|
|||
Password for ``username`` to log on to the APIC, using password-based authentication.
|
||||
|
||||
private_key
|
||||
Private key for ``username`` to log on to APIC, using signature-based authentication. *New in version 2.5*
|
||||
Private key for ``username`` to log on to APIC, using signature-based authentication.
|
||||
This could either be the raw private key content (include header/footer) or a file that stores the key content.
|
||||
*New in version 2.5*
|
||||
|
||||
certificate_name
|
||||
Name of the certificate in the ACI Web GUI. (Defaults to ``private_key`` file base name) *New in version 2.5*
|
||||
Name of the certificate in the ACI Web GUI.
|
||||
This defaults to either the ``username`` value or the ``private_key`` file base name).
|
||||
*New in version 2.5*
|
||||
|
||||
timeout
|
||||
Timeout value for socket-level communication.
|
||||
|
@ -367,11 +371,70 @@ You need the following parameters with your ACI module(s) for it to work:
|
|||
private_key: pki/admin.key
|
||||
certificate_name: admin # This could be left out !
|
||||
|
||||
or you can use the private key content:
|
||||
|
||||
.. code-block:: yaml
|
||||
:emphasize-lines: 2,3
|
||||
|
||||
username: admin
|
||||
private_key: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
<<your private key content>>
|
||||
-----END PRIVATE KEY-----
|
||||
certificate_name: admin # This could be left out !
|
||||
|
||||
|
||||
.. hint:: If you use a certificate name in ACI that matches the private key's basename, you can leave out the ``certificate_name`` parameter like the example above.
|
||||
|
||||
|
||||
Using Ansible Vault to encrypt the private key
|
||||
``````````````````````````````````````````````
|
||||
.. versionadded:: 2.8
|
||||
|
||||
To start, encrypt the private key and give it a strong password.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ansible-vault encrypt admin.key
|
||||
|
||||
Use a text editor to open the private-key. You should have an encrypted cert now.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
56484318584354658465121889743213151843149454864654151618131547984132165489484654
|
||||
45641818198456456489479874513215489484843614848456466655432455488484654848489498
|
||||
....
|
||||
|
||||
Copy and paste the new encrypted cert into your playbook as a new variable.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
private_key: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
56484318584354658465121889743213151843149454864654151618131547984132165489484654
|
||||
45641818198456456489479874513215489484843614848456466655432455488484654848489498
|
||||
....
|
||||
|
||||
Use the new variable for the private_key:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
username: admin
|
||||
private_key: "{{ private_key }}"
|
||||
certificate_name: admin # This could be left out !
|
||||
|
||||
When running the playbook, use "--ask-vault-pass" to decrypt the private key.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ansible-playbook site.yaml --ask-vault-pass
|
||||
|
||||
|
||||
More information
|
||||
````````````````
|
||||
Detailed information about Signature-based Authentication is available from `Cisco APIC Signature-Based Transactions <https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/kb/b_KB_Signature_Based_Transactions.html>`_.
|
||||
- Detailed information about Signature-based Authentication is available from `Cisco APIC Signature-Based Transactions <https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/kb/b_KB_Signature_Based_Transactions.html>`_.
|
||||
- More information on Ansible Vault can be found on the :ref:`Ansible Vault <vault>` page.
|
||||
|
||||
|
||||
.. _aci_guide_rest:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _vault:
|
||||
|
||||
Ansible Vault
|
||||
=============
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ def aci_argument_spec():
|
|||
port=dict(type='int', required=False),
|
||||
username=dict(type='str', default='admin', aliases=['user']),
|
||||
password=dict(type='str', no_log=True),
|
||||
private_key=dict(type='path', aliases=['cert_key']), # Beware, this is not the same as client_key !
|
||||
private_key=dict(type='str', aliases=['cert_key'], no_log=True), # Beware, this is not the same as client_key !
|
||||
certificate_name=dict(type='str', aliases=['cert_name']), # Beware, this is not the same as client_cert !
|
||||
output_level=dict(type='str', default='normal', choices=['debug', 'info', 'normal']),
|
||||
timeout=dict(type='int', default=30),
|
||||
|
@ -226,16 +226,39 @@ class ACIModule(object):
|
|||
if payload is None:
|
||||
payload = ''
|
||||
|
||||
# Use the private key basename (without extension) as certificate_name
|
||||
if self.params['certificate_name'] is None:
|
||||
self.params['certificate_name'] = os.path.basename(os.path.splitext(self.params['private_key'])[0])
|
||||
|
||||
try:
|
||||
with open(self.params['private_key'], 'r') as priv_key_fh:
|
||||
private_key_content = priv_key_fh.read()
|
||||
sig_key = load_privatekey(FILETYPE_PEM, private_key_content)
|
||||
except Exception:
|
||||
self.module.fail_json(msg='Cannot load private key %s' % self.params['private_key'])
|
||||
# Check if we got a private key. This allows the use of vaulting the private key.
|
||||
if self.params['private_key'].startswith('-----BEGIN PRIVATE KEY-----'):
|
||||
try:
|
||||
sig_key = load_privatekey(FILETYPE_PEM, self.params['private_key'])
|
||||
except Exception:
|
||||
self.module.fail_json(msg="Cannot load provided 'private_key' parameter.")
|
||||
# Use the username as the certificate_name value
|
||||
if self.params['certificate_name'] is None:
|
||||
self.params['certificate_name'] = self.params['username']
|
||||
elif self.params['private_key'].startswith('-----BEGIN CERTIFICATE-----'):
|
||||
self.module.fail_json(msg="Provided 'private_key' parameter value appears to be a certificate. Please correct.")
|
||||
else:
|
||||
# If we got a private key file, read from this file.
|
||||
# NOTE: Avoid exposing any other credential as a filename in output...
|
||||
if not os.path.exists(self.params['private_key']):
|
||||
self.module.fail_json(msg="The provided private key file does not appear to exist. Is it a filename?")
|
||||
try:
|
||||
with open(self.params['private_key'], 'r') as fh:
|
||||
private_key_content = fh.read()
|
||||
except Exception:
|
||||
self.module.fail_json(msg="Cannot open private key file '%s'." % self.params['private_key'])
|
||||
if private_key_content.startswith('-----BEGIN PRIVATE KEY-----'):
|
||||
try:
|
||||
sig_key = load_privatekey(FILETYPE_PEM, private_key_content)
|
||||
except Exception:
|
||||
self.module.fail_json(msg="Cannot load private key file '%s'." % self.params['private_key'])
|
||||
# Use the private key basename (without extension) as certificate_name
|
||||
if self.params['certificate_name'] is None:
|
||||
self.params['certificate_name'] = os.path.basename(os.path.splitext(self.params['private_key'])[0])
|
||||
elif private_key_content.startswith('-----BEGIN CERTIFICATE-----'):
|
||||
self.module.fail_json(msg="Provided private key file %s appears to be a certificate. Please correct." % self.params['private_key'])
|
||||
else:
|
||||
self.module.fail_json(msg="Provided private key file '%s' does not appear to be a private key. Please correct." % self.params['private_key'])
|
||||
|
||||
# NOTE: ACI documentation incorrectly adds a space between method and path
|
||||
sig_request = method + path + payload
|
||||
|
@ -312,7 +335,7 @@ class ACIModule(object):
|
|||
self.url = '%(protocol)s://%(host)s/' % self.params + path.lstrip('/')
|
||||
|
||||
# Sign and encode request as to APIC's wishes
|
||||
if self.params['private_key'] is not None:
|
||||
if not self.params['private_key']:
|
||||
self.cert_auth(path=path, payload=payload)
|
||||
|
||||
# Perform request
|
||||
|
@ -349,7 +372,7 @@ class ACIModule(object):
|
|||
self.url = '%(protocol)s://%(host)s/' % self.params + path.lstrip('/')
|
||||
|
||||
# Sign and encode request as to APIC's wishes
|
||||
if self.params['private_key'] is not None:
|
||||
if not self.params['private_key']:
|
||||
self.cert_auth(path=path, method='GET')
|
||||
|
||||
# Perform request
|
||||
|
@ -636,7 +659,7 @@ class ACIModule(object):
|
|||
|
||||
elif not self.module.check_mode:
|
||||
# Sign and encode request as to APIC's wishes
|
||||
if self.params['private_key'] is not None:
|
||||
if not self.params['private_key']:
|
||||
self.cert_auth(method='DELETE')
|
||||
|
||||
resp, info = fetch_url(self.module, self.url,
|
||||
|
@ -772,7 +795,7 @@ class ACIModule(object):
|
|||
uri = self.url + self.filter_string
|
||||
|
||||
# Sign and encode request as to APIC's wishes
|
||||
if self.params['private_key'] is not None:
|
||||
if not self.params['private_key']:
|
||||
self.cert_auth(path=self.path + self.filter_string, method='GET')
|
||||
|
||||
resp, info = fetch_url(self.module, uri,
|
||||
|
@ -873,7 +896,7 @@ class ACIModule(object):
|
|||
return
|
||||
elif not self.module.check_mode:
|
||||
# Sign and encode request as to APIC's wishes
|
||||
if self.params['private_key'] is not None:
|
||||
if not self.params['private_key']:
|
||||
self.cert_auth(method='POST', payload=json.dumps(self.config))
|
||||
|
||||
resp, info = fetch_url(self.module, self.url,
|
||||
|
|
|
@ -34,16 +34,17 @@ options:
|
|||
required: yes
|
||||
private_key:
|
||||
description:
|
||||
- PEM formatted file that contains your private key to be used for signature-based authentication.
|
||||
- The name of the key (without extension) is used as the certificate name in ACI, unless C(certificate_name) is specified.
|
||||
- Either a PEM-formatted private key file or the private key content used for signature-based authentication.
|
||||
- This value also influences the default C(certificate_name) that is used.
|
||||
- This option is mutual exclusive with C(password). If C(password) is provided too, it will be ignored.
|
||||
type: path
|
||||
type: str
|
||||
required: yes
|
||||
aliases: [ cert_key ]
|
||||
certificate_name:
|
||||
description:
|
||||
- The X.509 certificate name attached to the APIC AAA user used for signature-based authentication.
|
||||
- It defaults to the C(private_key) basename, without extension.
|
||||
- If a C(private_key) filename was provided, this defaults to the C(private_key) basename, without extension.
|
||||
- If PEM-formatted content was provided for C(private_key), this defaults to the C(username) value.
|
||||
type: str
|
||||
aliases: [ cert_name ]
|
||||
output_level:
|
||||
|
|
Loading…
Reference in a new issue