Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/letsencrypt #147

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ pvecluster. Here, a file lookup is used to read the contents of a file in the
playbook, e.g. `files/pve01/lab-node01.key`. You could possibly just use host
variables instead of files, if you prefer.

`pve_ssl_letsencrypt` allows to obtain a Let's Encrypt SSL certificate for
pvecluster. The Ansible role [systemli.letsencrypt](https://galaxy.ansible.com/systemli/letsencrypt/)
needs to be installed first in order to use this function.
`pve_acme_enabled` allows to obtain an acmle SSL certificate for pve nodes. If
enabled, you also need to configure `pve_acme_contact` (your mail address) and
`pve_acme_directory` (url to acme service).

`pve_cluster_enabled` enables the role to perform all cluster management tasks.
This includes creating a cluster if it doesn't exist, or adding nodes to the
Expand Down Expand Up @@ -404,7 +404,9 @@ pve_ceph_fs: [] # List of CephFS filesystems to create
pve_ceph_crush_rules: [] # List of CRUSH rules to create
# pve_ssl_private_key: "" # Should be set to the contents of the private key to use for HTTPS
# pve_ssl_certificate: "" # Should be set to the contents of the certificate to use for HTTPS
pve_ssl_letsencrypt: false # Specifies whether or not to obtain a SSL certificate using Let's Encrypt
pve_acme_enabled: no # If enabled, role tries to add a SSL certificate for webui via ACME provider
# pve_acme_directory: # ACME directory to register on/get certificates from
# pve_acme_contact: # email address for ACME provider
pve_roles: [] # Added more roles with specific privileges. See section on User Management.
pve_groups: [] # List of group definitions to manage in PVE. See section on User Management.
pve_users: [] # List of user definitions to manage in PVE. See section on User Management.
Expand Down
5 changes: 4 additions & 1 deletion defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ pve_manage_hosts_enabled: yes
# pve_cluster_addr1: "{{ ansible_eth1.ipv4.address }}
pve_datacenter_cfg: {}
pve_cluster_ha_groups: []
pve_ssl_letsencrypt: false
pve_acme_enabled: no
# pve_acme_directory: "ACME directory to register on/get certificates from"
# pve_acme_contact: "email address for Let's Encrypt"

# additional roles for your cluster (f.e. for monitoring)
pve_roles: []
pve_groups: []
Expand Down
203 changes: 203 additions & 0 deletions library/proxmox_acme_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

ANSIBLE_METADATA = {
'metadata_version': '1.0',
'status': ['stableinterface'],
'supported_by': 'lae'
}

DOCUMENTATION = '''
---
module: proxmox_acme_account
short_description: Manages ACME configs/account registrations in Proxmox
options:
name:
default: "default"
description:
- Name of the ACME config to manage.
state:
required: false
default: "present"
choices: [ "present", "absent" ]
description:
- Specifies whether the ACME account config should exist or not.
directory:
required: false
default: https://acme-v02.api.letsencrypt.org/directory
description:
- Specifies which ACME directory to register against.
contact:
required: true
description:
- Sets the contact email for the ACME account.
author:
- Musee Ullah (@lae)
'''

EXAMPLES = '''
- name: Create default ACME configuration
proxmox_acme_account:
contact: [email protected]
- name: Create secondary ACME configuration on staging
proxmox_acme_account:
name: secondary
directory: https://acme-staging-v02.api.letsencrypt.org/directory
contact: [email protected]
'''

RETURN = '''
updated_fields:
description: Fields that were modified for an existing account config
type: list
account:
description: Information about the account fetched from PVE after this task completed. (output for this is currently disabled since it contains a private key)
type: json
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text
from ansible.module_utils.pvesh import ProxmoxShellError
import ansible.module_utils.pvesh as pvesh

class ProxmoxACMEAccount(object):
def __init__(self, module):
self.module = module
self.name = module.params['name']
self.state = module.params['state']
self.directory = module.params['directory']
self.contact = module.params['contact']

def lookup(self):
try:
return pvesh.get("cluster/acme/account/{}".format(self.name))
except ProxmoxShellError as e:
if e.status_code == 400:
return None
self.module.fail_json(msg=e.message, status_code=e.status_code)

def identify_tos(self):
try:
return pvesh.get("cluster/acme/tos")
except ProxmoxShellError as e:
self.module.fail_json(msg=e.message, status_code=e.status_code)

def remove_account(self):
try:
pvesh.delete("cluster/acme/account/{}".format(self.name))
return (True, None)
except ProxmoxShellError as e:
return (False, e.message)

def create_account(self):
new_account = {
'name': self.name,
'directory': self.directory,
'contact': self.contact,
'tos_url': self.identify_tos()
}

try:
pvesh.create("cluster/acme/account/", **new_account)
return (True, None)
except ProxmoxShellError as e:
return (False, e.message)

def modify_account(self):
lookup = self.lookup()

updated_fields = []
recreate_account = False
error = None

if lookup['account']['contact'][0].split(':')[1] != self.contact:
updated_fields.append('contact')

if lookup['directory'] != self.directory:
updated_fields.append('directory')
recreate_account = True

if lookup['tos'] != self.identify_tos():
updated_fields.append('tos')
recreate_account = True

if self.module.check_mode:
self.module.exit_json(changed=bool(updated_fields), expected_changes=updated_fields, would_recreate=recreate_account)

if not updated_fields:
# No changes necessary
return (updated_fields, recreate_account, error)

try:
if recreate_account:
self.remove_account()
self.create_account()
else:
pvesh.set("cluster/acme/account/{}".format(self.name), contact=self.contact)
except ProxmoxShellError as e:
error = e.message

return (updated_fields, recreate_account, error)

def main():
# Refer to https://pve.proxmox.com/pve-docs/api-viewer/index.html
module = AnsibleModule(
argument_spec = dict(
name=dict(default='default', type='str'),
state=dict(choices=['present', 'absent'], default='present', type='str'),
directory=dict(default='https://acme-v02.api.letsencrypt.org/directory', type='str'),
contact=dict(default=None, required=True, type='str'),
),
supports_check_mode=True
)

account = ProxmoxACMEAccount(module)

changed = False
error = None
result = {}
result['name'] = account.name
result['state'] = account.state

if account.state == 'absent':
if account.lookup() is not None:
if module.check_mode:
module.exit_json(changed=True)

(changed, error) = account.remove_account()

if error is not None:
module.fail_json(name=account.name, msg=error)
elif account.state == 'present':
if not account.lookup():
if module.check_mode:
module.exit_json(changed=True)

(changed, error) = account.create_account()
else:
# modify account (note: this function is check mode aware)
(updated_fields, recreate_account, error) = account.modify_account()

if updated_fields:
changed = True
result['updated_fields'] = updated_fields
result['account_recreated'] = recreate_account

if error is not None:
module.fail_json(name=account.name, msg=error)

"""
# The following contains sensitive data, so for now don't include it in the
# module output. I think there's a flag to check for that lets you decide
# how to handle outputting of sensitive output that we should use here.
lookup = account.lookup()
if lookup is not None:
result['account'] = lookup
"""

result['changed'] = changed

module.exit_json(**result)

if __name__ == '__main__':
main()
5 changes: 5 additions & 0 deletions tasks/acme_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- name: Create the default ACME account
proxmox_acme_account:
contact: "{{ pve_acme_contact }}"
directory: "{{ pve_acme_directory | default(omit) }}"
4 changes: 2 additions & 2 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -303,5 +303,5 @@
- "pve_ssl_private_key is defined"
- "pve_ssl_certificate is defined"

- import_tasks: ssl_letsencrypt.yml
when: "pve_ssl_letsencrypt | bool"
- import_tasks: acme_config.yml
when: pve_acme_enabled
18 changes: 0 additions & 18 deletions tasks/ssl_letsencrypt.yml

This file was deleted.

12 changes: 12 additions & 0 deletions tasks/validate_variables.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
- name: Ensure ACME is not enabled when an SSL key/certificate is provided
fail:
msg: "You cannot use ACME/Let's Encrypt while using your own private key/certificate."
when:
- pve_ssl_private_key is defined and pve_acme_enabled

- name: Ensure contact email is specified when using Let's Encrypt
fail:
msg: "Please set pve_acme_contact to a valid email."
when:
- pve_acme_enabled and pve_acme_contact is not defined
5 changes: 0 additions & 5 deletions templates/pve-letsencrypt-post-hook.sh.j2

This file was deleted.