Skip to content

Commit

Permalink
Merge pull request #484 from dezeroku/hashivault_pki_role_diff_mode
Browse files Browse the repository at this point in the history
Add diff mode support to hashivault_pki_role and update options list
  • Loading branch information
TerryHowe authored Jul 9, 2024
2 parents a8fc2e0 + 9cd19e8 commit e37b89b
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 33 deletions.
20 changes: 20 additions & 0 deletions ansible/module_utils/hashivault.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,23 @@ def is_state_changed(desired_state, current_state, ignore=None):
:rtype: bool
"""
return(len(get_keys_updated(desired_state, current_state)) > 0)


def parse_duration(duration, fallback=None):
if isinstance(duration, int):
return duration
elif not isinstance(duration, str):
return fallback

if duration.endswith('d'):
return int(duration[:-1]) * 60 * 60 * 24
if duration.endswith('h'):
return int(duration[:-1]) * 60 * 60
if duration.endswith('m'):
return int(duration[:-1]) * 60
if duration.endswith('s'):
return int(duration[:-1])
if duration != "":
return int(duration)

return fallback
109 changes: 100 additions & 9 deletions ansible/modules/hashivault/hashivault_pki_role.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import copy

from ansible.module_utils.hashivault import hashivault_argspec
from ansible.module_utils.hashivault import hashivault_auth_client
from ansible.module_utils.hashivault import hashivault_init
from ansible.module_utils.hashivault import hashivault_normalize_from_doc
from ansible.module_utils.hashivault import hashiwrapper
from ansible.module_utils.hashivault import is_state_changed
from ansible.module_utils.hashivault import parse_duration

ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'version': '1.1'}
DOCUMENTATION = r'''
Expand All @@ -25,7 +27,7 @@
description:
- location where secrets engine is mounted. also known as path
name:
recuired: true
required: true
description:
- Specifies the name of the role to create.
role_file:
Expand Down Expand Up @@ -96,6 +98,13 @@
description:
- Allows names specified in `allowed_domains` to contain glob patterns (e.g. `ftp*.example.com`)
- Clients will be allowed to request certificates with names matching the glob patterns.
allow_wildcard_certificates:
type: bool
default: true
description:
- Allows the issuance of certificates with RFC 6125 wildcards in the CN field.
- When set to false, this prevents wildcards from being issued even if they would've
- been allowed by an option above.
allow_any_name:
type: bool
default: false
Expand Down Expand Up @@ -124,6 +133,12 @@
- Values can contain glob patterns (e.g. `spiffe://hostname/*`).
- Although this parameter could take a string with comma-delimited items, it's highly advised to
not do so as it would break idempotency.
allowed_uri_sans_template:
type: bool
default: false
description:
- When set, allowed_uri_sans may contain templates, as with ACL Path Templating.
- Non-templated domains are also still permitted.
allowed_other_sans:
type: list
description:
Expand All @@ -135,6 +150,14 @@
`(bool)` Specifies if certificates are flagged for server use.
- Although this parameter could take a string with comma-delimited items, it's highly advised to
not do so as it would break idempotency.
allowed_serial_numbers:
type: list
default: ""
description:
- If set, an array of allowed serial numbers to be requested during certificate issuance.
- These values support shell-style globbing.
- When empty, custom-specified serial numbers will be forbidden.
- It is strongly recommended to allow Vault to generate random serial numbers instead.
server_flag:
type: bool
default: true
Expand Down Expand Up @@ -165,10 +188,26 @@
keys of either type and with any bit size (subject to > 1024 bits for RSA keys).
key_bits:
type: int
default: 2048
default: 0
description:
- Specifies the number of bits to use for the generated keys.
- Allowed values are 0 (universal default);
- with key_type=rsa, allowed values are: 2048 (default), 3072, 4096 or 8192;
- with key_type=ec, allowed values are: 224, 256 (default), 384, or 521;
- ignored with key_type=ed25519 or in signing operations when key_type=any.
signature_bits:
type: int
default: 0
description:
- Specifies the number of bits to use for the generated keys
- This will need to be changed for `ec` keys, e.g., 224 or 521.
use_pss:
type: bool
default: false
description:
- Specifies whether or not to use PSS signatures over PKCS#1v1.5 signatures when a RSA-type issuer
- is used.
- Ignored for ECDSA/Ed25519 issuers.
key_usage:
type: list
default: ["DigitalSignature", "KeyAgreement", "KeyEncipherment"]
Expand Down Expand Up @@ -294,6 +333,25 @@
default: "30s"
description:
- Specifies the duration by which to backdate the NotBefore property.
not_after:
type: string
description:
- Set the Not After field of the certificate with specified date value.
- The value format should be given in UTC format YYYY-MM-ddTHH:MM:SSZ.
- Supports the Y10K end date for IEEE 802.1AR-2018 standard devices, 9999-12-31T23:59:59Z.
cn_validations:
type: list
default: ["email", "hostname"]
description:
- Validations to run on the Common Name field of the certificate.
allowed_user_ids:
type: string
default: ""
description:
- Comma separated, globbing list of User ID Subject components to allow on requests.
- By default, no user IDs are allowed.
- Use the bare wildcard * value to allow any value.
- See also the user_ids request parameter.
extends_documentation_fragment:
- hashivault
'''
Expand Down Expand Up @@ -358,6 +416,24 @@ def hashivault_pki_role(module):
except Exception as e:
return e.args[0]

# For EC and ED25519 this field is ignored and leads to misleading diff.
if desired_state.get("key_type", None) in ("ed25519", "ec"):
desired_state.pop("signature_bits", None)

# Normalize some keys. This is a quirk of the vault api that it
# expects a different data format in the PUT/POST endpoint than
# it returns in the GET endpoint.
# Thus we'll keep desired_state_comp for the diff purposes and use
# desired_state as the actual params to be POSTed
desired_state_comp = copy.deepcopy(desired_state)

if desired_state_comp.get('ttl', None):
desired_state_comp['ttl'] = parse_duration(desired_state_comp['ttl'])
if desired_state_comp.get('max_ttl', None):
desired_state_comp['max_ttl'] = parse_duration(desired_state_comp['max_ttl'])
if desired_state_comp.get('not_before_duration', None):
desired_state_comp['not_before_duration'] = parse_duration(desired_state_comp['not_before_duration'])

changed = False
try:
current_state = client.secrets.pki.read_role(name=name, mount_point=mount_point).get('data')
Expand All @@ -369,18 +445,33 @@ def hashivault_pki_role(module):
if (exists and state == 'absent') or (not exists and state == 'present'):
changed = True

# compare current_state to desired_state
if exists and state == 'present' and not changed:
changed = is_state_changed(desired_state, current_state)
# compare current_state to desired_state_comp
if exists and state == "present" and not changed:
# Update all keys not present in the desired_state_comp with data from the
# current_state, to ensure a proper diff output.
for key in current_state:
if key not in desired_state_comp:
desired_state_comp[key] = current_state[key]

changed = desired_state_comp != current_state

# make the changes!
if changed and state == 'present' and not module.check_mode:
client.secrets.pki.create_or_update_role(name=name, mount_point=mount_point, extra_params=desired_state)

elif changed and state == 'absent' and not module.check_mode:
client.secrets.pki.delete_role(name=name, mount_point=mount_point)
elif changed and state == 'absent':
if not module.check_mode:
client.secrets.pki.delete_role(name=name, mount_point=mount_point)
# after deleting it the item is no more
desired_state_comp = {}

return {'changed': changed}
return {
"changed": changed,
"diff": {
"before": current_state,
"after": desired_state_comp,
},
}


if __name__ == '__main__':
Expand Down
26 changes: 4 additions & 22 deletions ansible/modules/hashivault/hashivault_secret_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ansible.module_utils.hashivault import hashivault_auth_client
from ansible.module_utils.hashivault import hashivault_init
from ansible.module_utils.hashivault import hashiwrapper
from ansible.module_utils.hashivault import parse_duration

DEFAULT_TTL = 2764800
ANSIBLE_METADATA = {'status': ['stableinterface'], 'supported_by': 'community', 'version': '1.1'}
Expand Down Expand Up @@ -125,26 +126,6 @@ def main():
module.exit_json(**result)


def parse_duration(duration):
if isinstance(duration, int):
return duration
elif not isinstance(duration, str):
return DEFAULT_TTL

if duration.endswith('d'):
return int(duration[:-1]) * 60 * 60 * 24
if duration.endswith('h'):
return int(duration[:-1]) * 60 * 60
if duration.endswith('m'):
return int(duration[:-1]) * 60
if duration.endswith('s'):
return int(duration[:-1])
if duration != "":
return int(duration)

return DEFAULT_TTL


@hashiwrapper
def hashivault_secret_engine(module):
params = module.params
Expand All @@ -154,9 +135,10 @@ def hashivault_secret_engine(module):
description = params.get('description')
config = params.get('config')
if 'default_lease_ttl' in config:
config['default_lease_ttl'] = parse_duration(config['default_lease_ttl'])
config['default_lease_ttl'] = parse_duration(config['default_lease_ttl'], DEFAULT_TTL)
if 'max_lease_ttl' in config:
config['max_lease_ttl'] = parse_duration(config['max_lease_ttl'])
config['max_lease_ttl'] = parse_duration(config['max_lease_ttl'],
DEFAULT_TTL)
if params.get('state') in ['present', 'enabled']:
state = 'enabled'
else:
Expand Down
92 changes: 90 additions & 2 deletions functional/test_pki.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

- debug:
msg: "mount_root:\t{{ mount_root }}\nmount_inter:\t{{ mount_inter }}\nrole_name:\t{{ role }}"
- name: Enabele PKI secrets engine
- name: Enable PKI secrets engine
hashivault_secret_engine:
name: "{{mount_root}}"
backend: "pki"
Expand Down Expand Up @@ -56,7 +56,7 @@
- response.rc == 0
- response.changed == False

- name: Enabele PKI secrets engine
- name: Enable PKI secrets engine
hashivault_secret_engine:
name: "{{mount_inter}}"
backend: "pki"
Expand Down Expand Up @@ -253,15 +253,80 @@
that:
- response.rc == 0
- response.changed == True
- name: Create/Update Role check_mode
hashivault_pki_role:
mount_point: "{{mount_inter}}"
name: "{{role}}"
check_mode: true
register: response
- assert:
that:
- response.rc == 0
- response.changed == False
- response.diff.before == response.diff.after
- name: Create/Update Role
hashivault_pki_role:
mount_point: "{{mount_inter}}"
name: "{{role}}"
register: response
- assert:
that:
- response.rc == 0
- response.changed == False
- response.diff.before == response.diff.after
- name: Create/Update Role
hashivault_pki_role:
mount_point: "{{mount_inter}}"
name: "{{role}}"
config:
max_ttl: "153"
ttl: "150"
not_before_duration: "45s"
register: response
- assert:
that:
- response.rc == 0
- response.changed == True
- response.diff.before.max_ttl != response.diff.after.max_ttl
- response.diff.before.ttl != response.diff.after.ttl
- response.diff.before.not_before_duration != response.diff.after.not_before_duration
- name: Create/Update Role
hashivault_pki_role:
mount_point: "{{mount_inter}}"
name: "{{role}}"
config:
max_ttl: "153"
ttl: "150"
not_before_duration: "45s"
register: response
- assert:
that:
- response.rc == 0
- response.changed == False
- response.diff.before == response.diff.after
- name: Create/Update Role check_mode
hashivault_pki_role:
mount_point: "{{mount_inter}}"
name: "{{role}}"
config:
allow_bare_domains: True
allow_subdomains: True
allow_any_name: True
not_before_duration: "15s"
check_mode: true
register: response
- assert:
that:
- response.rc == 0
- response.changed == True
- response.diff.before != response.diff.after
- response.diff.before.allow_bare_domains == False
- response.diff.after.allow_bare_domains == True
- response.diff.before.allow_subdomains == False
- response.diff.after.allow_subdomains == True
- response.diff.before.allow_any_name == False
- response.diff.after.allow_any_name == True
- response.diff.before.not_before_duration != response.diff.after.not_before_duration
- name: Create/Update Role
hashivault_pki_role:
mount_point: "{{mount_inter}}"
Expand All @@ -276,6 +341,29 @@
that:
- response.rc == 0
- response.changed == True
- response.diff.before != response.diff.after
- response.diff.before.allow_bare_domains == False
- response.diff.after.allow_bare_domains == True
- response.diff.before.allow_subdomains == False
- response.diff.after.allow_subdomains == True
- response.diff.before.allow_any_name == False
- response.diff.after.allow_any_name == True
- response.diff.before.not_before_duration != response.diff.after.not_before_duration
- name: Create/Update Role, no diff
hashivault_pki_role:
mount_point: "{{mount_inter}}"
name: "{{role}}"
config:
allow_bare_domains: True
allow_subdomains: True
allow_any_name: True
not_before_duration: "15s"
register: response
- assert:
that:
- response.rc == 0
- response.changed == False
- response.diff.before == response.diff.after

- name: List Roles check_mode expect_fail
hashivault_pki_role_list:
Expand Down

0 comments on commit e37b89b

Please sign in to comment.