Skip to content

feat: add nginx upstream module #163

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

Open
wants to merge 3 commits into
base: latest
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
92 changes: 92 additions & 0 deletions docs/source/modules/nginx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Nginx
**STATE**: unstable

**TESTS**: `nginx_upstream_server <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/nginx_upstream_server.yml>`_
`nginx_upstream <https://github.com/ansibleguy/collection_opnsense/blob/latest/tests/nginx_upstream.yml>`_

**API Docs**: `Plugins - Nginx <https://docs.opnsense.org/development/api/plugins/nginx.html>`_

Expand Down Expand Up @@ -66,6 +67,35 @@ ansibleguy.opnsense.nginx_upstream_server
"reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst


ansibleguy.opnsense.nginx_upstream
=========================================

.. csv-table:: Definition
:header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment"
:widths: 15 10 10 10 10 45

"description","string","true","\-","name","\-"
"serverentries","list","true","\-","\-","List of upstream servers"
"load_balancing_algorithm","string","false","\-","\-","\-"
"keepalive","integer","false","\-","\-","\-"
"keepalive_requests","integer","false","\-","\-","\-"
"keepalive_timeout","integer","false","\-","\-","\-"
"host_port","integer","false","\-","\-","\-"
"x_forwarded_host_verbatim","boolean","false","\-","\-","\-"
"proxy_protocol","boolean","false","\-","\-","\-"
"store","boolean","false","\-","\-","Store the response on the local storage."
"tls_enable","boolean","false","\-","\-","Use TLS (HTTPS) to connect to the server."
"tls_client_certificate","string","false","\-","\-","A certificate to use for this upstream."
"tls_name_override","string","false","\-","\-","\-"
"tls_protocol_versions","list","false","\-","\-","List of support TLS versions TLSv1, TLSv1.1, TLSv1.2, TLSv1.3"
"tls_session_reuse","boolean","true","\-","\-","\-"
"tls_trusted_certificate","string","false","\-","\-","A certificate authority to use for this upstream."
"tls_verify","boolean","false","\-","\-","\-"
"tls_verify_depth","integer","false","1","\-","\-"
"state","string","false","present","\-","Choice of 'present' or 'absent'."
"reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst


Usage
*****

Expand Down Expand Up @@ -116,3 +146,65 @@ ansibleguy.opnsense.nginx_upstream_server
- name: Printing
ansible.builtin.debug:
var: existing_servers.data

ansibleguy.opnsense.nginx_upstream
=========================================

.. code-block:: yaml

- hosts: localhost
gather_facts: false
module_defaults:
group/ansibleguy.opnsense.all:
firewall: 'opnsense.template.ansibleguy.net'
api_credential_file: '/home/guy/.secret/opn.key'

ansibleguy.opnsense.list:
target: 'nginx_upstream'

tasks:
- name: Add an upstream server
ansibleguy.opnsense.nginx_upstream_server:
name: 'upstreamserver1'
server: '192.168.1.1'
port: 80
priority: 1
max_conns: 100
max_fails: 50
fail_timeout: 10
no_use: 'down'

- name: Add an upstream
ansibleguy.opnsense.nginx_upstream:
name: 'upstream1'
serverentries: ['upstreamserver1']
load_balancing_algorithm: 'ip_hash'
keepalive: 1
keepalive_requests: 100
keepalive_timeout: 10
host_port: 80
x_forwarded_host_verbatim: true
proxy_protocol: false
store: false
tls_enable: true
# tls_client_certificate: "example.com (ACME Client)"
tls_protocol_versions: ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
tls_session_reuse: true
tls_verify: false
tls_verify_depth: 1
# state: 'present'
# reload: true

- name: Changing the upstream
ansibleguy.opnsense.nginx_upstream:
name: 'upstream1'
serverentries: ['192.168.1.100']

- name: Listing upstreams
ansibleguy.opnsense.list:
target: 'nginx_upstream'
register: existing_upstreams

- name: Printing
ansible.builtin.debug:
var: existing_upstreams.data
2 changes: 1 addition & 1 deletion meta/runtime.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
---

requires_ansible: ">=2.14"

action_groups:
Expand Down Expand Up @@ -89,6 +88,7 @@ action_groups:
- ansibleguy.opnsense.webproxy_pac_rule
nginx:
- ansibleguy.opnsense.nginx_general
- ansibleguy.opnsense.nginx_upstream
- ansibleguy.opnsense.nginx_upstream_server
route:
- ansibleguy.opnsense.route
Expand Down
166 changes: 166 additions & 0 deletions plugins/module_utils/main/nginx_upstream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import Session
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import is_unset, validate_int_fields

class Upstream(BaseModule):
FIELD_ID = 'description'
CMDS = {
'add': 'addupstream',
'del': 'delupstream',
'detail': 'getupstream',
'search': 'searchupstream',
'set': 'setupstream',
}
API_KEY_PATH = 'upstream'
API_MOD = 'nginx'
API_CONT = 'settings'
API_CONT_REL = 'service'
API_CMD_REL = 'reconfigure'
FIELDS_CHANGE = [
'description', 'serverentries', 'load_balancing_algorithm', 'keepalive',
'keepalive_requests', 'keepalive_timeout', 'host_port',
'x_forwarded_host_verbatim', 'proxy_protocol', 'store', 'tls_enable',
'tls_client_certificate', 'tls_name_override', 'tls_protocol_versions',
'tls_session_reuse', 'tls_trusted_certificate', 'tls_verify',
'tls_verify_depth'
]
FIELDS_ALL = FIELDS_CHANGE
FIELDS_TYPING = {
'bool': ['x_forwarded_host_verbatim', 'proxy_protocol', 'store',
'tls_enable', 'tls_session_reuse', 'tls_verify'],
'list': ['serverentries', 'tls_protocol_versions', 'tls_trusted_certificate'],
'int': ['keepalive', 'keepalive_requests', 'keepalive_timeout',
'host_port', 'tls_verify_depth'],
'select': ['load_balancing_algorithm'],
}
INT_VALIDATIONS = {
'keepalive': {'min': 0, 'max': 1000000000},
'keepalive_requests': {'min': 1, 'max': 1000000000},
'keepalive_timeout': {'min': 1, 'max': 1000000000},
'host_port': {'min': 1, 'max': 65535},
'tls_verify_depth': {'min': 1, 'max': 1000000000},
}
FIELDS_IGNORE = []
FIELDS_REQUIRED = ['description', 'serverentries']
FIELDS_DEFAULTS = {
'x_forwarded_host_verbatim': False,
'keepalive_requests': 1000,
'keepalive_timeout': 75,
'proxy_protocol': False,
'store': False,
'tls_enable': False,
'tls_session_reuse': True,
'tls_verify': True,
'tls_verify_depth': 1,
}
EXIST_ATTR = 'upstream'

def __init__(self, module: AnsibleModule, result: dict, session: Session = None):
BaseModule.__init__(self=self, m=module, r=result, s=session)
self.upstream = {}
self.existing_upstream_servers = None
self.existing_tls_client_certificates = None
self.existing_tls_trusted_certificates = None

def check(self):
if self.p['state'] == 'present':
if is_unset(self.p['description']) or is_unset(self.p['serverentries']):
self.m.fail_json("You need to provide a 'description' and 'serverentries' to create an upstream!")

validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS)

if 'tls_protocol_versions' in self.p and self.p['tls_protocol_versions']:
valid_versions = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
for version in self.p['tls_protocol_versions']:
if version not in valid_versions:
self.m.fail_json(
f"Invalid TLS protocol version: '{version}'. "
f"Valid versions are: {', '.join(valid_versions)}"
)

if 'load_balancing_algorithm' in self.p and self.p['load_balancing_algorithm']:
if self.p['load_balancing_algorithm'] not in ['ip_hash']:
self.m.fail_json(
f"Invalid load balancing algorithm: '{self.p['load_balancing_algorithm']}'. "
"Valid algorithms are: 'ip_hash'"
)

self._base_check()

if self.p['state'] == 'present':
self._resolve_relations()

def _search_uppstream_servers(self) -> None:
self.existing_upstream_servers = self.s.get(cnf={
**self.call_cnf, **{'command': 'searchupstreamserver', 'controller': 'settings'}
})['rows']

def _search_tls_client_certificates(self) -> None:
self.existing_tls_client_certificates = self.s.get(cnf={
**{'module': 'trust', 'command': 'search', 'controller': 'cert'}
})['rows']

def _search_tls_trusted_certificates(self) -> None:
self.existing_tls_trusted_certificates = self.s.get(cnf={
**{'module': 'trust', 'command': 'search', 'controller': 'ca'}
})['rows']

def _resolve_relations(self) -> None:
if not is_unset(self.p['serverentries']):
self._search_uppstream_servers()
mapping = {
server['description']: server['uuid']
for server in self.existing_upstream_servers
}

missing = [
entry
for entry in self.p['serverentries']
if entry not in mapping
]
if any(missing):
self.m.fail_json(f"Server entries {missing.join(',')} do not exist!")

self.p['serverentries'] = [
mapping[entry]
for entry in self.p['serverentries']
]

if not is_unset(self.p['tls_client_certificate']):
self._search_tls_client_certificates()
mapping = {
certificate['descr']: certificate['refid']
for certificate in self.existing_tls_client_certificates
}

tls_client_certificate = self.p['tls_client_certificate']
if tls_client_certificate not in mapping:
self.m.fail_json(f"TLS client certificate {tls_client_certificate} does not exist!")

self.p['tls_client_certificate'] = mapping[tls_client_certificate]

if not is_unset(self.p['tls_trusted_certificate']):
self._search_tls_trusted_certificates()
mapping = {
ca['descr']: ca['refid']
for ca in self.existing_tls_trusted_certificates
}

missing = [
entry
for entry in self.p['tls_trusted_certificate']
if entry not in mapping
]
if any(missing):
self.m.fail_json(f"TLS trusted certificates {missing.join(',')} do not exist!")

self.p['tls_trusted_certificate'] = [
mapping[entry]
for entry in self.p['tls_trusted_certificate']
]


def update(self) -> None:
self.b.update(enable_switch=False)

6 changes: 5 additions & 1 deletion plugins/modules/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
'webproxy_traffic', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule',
'cron', 'unbound_dot', 'ipsec_cert', 'ipsec_psk', 'source_nat', 'frr_bgp_prefix_list', 'frr_bgp_community_list',
'frr_bgp_as_path', 'frr_bgp_route_map', 'frr_ospf_prefix_list', 'frr_ospf_route_map', 'webproxy_forward',
'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'nginx_upstream_server', 'ipsec_connection', 'ipsec_pool',
'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'nginx_upstream_server', 'nginx_upstream', 'ipsec_connection', 'ipsec_pool',
'ipsec_child', 'ipsec_vti', 'ipsec_auth_local', 'ipsec_auth_remote', 'frr_general', 'unbound_general',
'unbound_acl', 'ids_general', 'ids_policy', 'ids_rule', 'ids_ruleset', 'ids_user_rule', 'ids_policy_rule',
'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', 'dhcrelay_destination', 'dhcrelay_relay',
Expand Down Expand Up @@ -328,6 +328,10 @@ def run_module():
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.nginx_upstream_server import \
UpstreamServer as Target_Obj

elif target == 'nginx_upstream':
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.nginx_upstream import \
Upstream as Target_Obj

elif target == 'ipsec_connection':
from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.ipsec_connection import \
Connection as Target_Obj
Expand Down
Loading