saltext-opnsense 0.1.1.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ try:
2
+ from saltext.opnsense.version._version import __version__
3
+ except Exception:
4
+ __version__ = "0.0.0.dev"
5
+
6
+ __all__ = ["__version__"]
@@ -0,0 +1,46 @@
1
+ import logging
2
+
3
+ log = logging.getLogger(__name__)
4
+
5
+ __virtualname__ = "opnsense"
6
+
7
+
8
+ def __virtual__():
9
+ if "__proxy__" not in globals():
10
+ return (False, "opnsense grains only run on proxy minions")
11
+ return __virtualname__
12
+
13
+
14
+ def opnsense_grains():
15
+ grains = {}
16
+ try:
17
+ if "__salt__" in globals() and "opnsense.ping" in __salt__:
18
+ try:
19
+ if not __salt__["opnsense.ping"]():
20
+ return grains
21
+ except Exception:
22
+ pass
23
+ try:
24
+ spec = __salt__["opnsense.spec"]()
25
+ if isinstance(spec, dict):
26
+ mods = spec.get("modules", {})
27
+ meta = spec.get("meta", {})
28
+ grains["opnsense_api_modules"] = list(mods.keys()) if isinstance(mods, dict) else list(mods)
29
+ grains["opnsense_spec_version"] = meta.get("core_ref", "25.7")
30
+ except Exception:
31
+ pass
32
+ try:
33
+ fw_status = __salt__["opnsense.call"]("core", "firmware", "status", data={}, method="POST")
34
+ if isinstance(fw_status, dict):
35
+ grains["opnsense_version"] = fw_status.get("product_version") or fw_status.get("product_version_string")
36
+ except Exception:
37
+ pass
38
+ try:
39
+ alias_res = __salt__["opnsense.search"]("unbound", "settings", "host_alias", row_count=1)
40
+ if isinstance(alias_res, dict):
41
+ grains["opnsense_unbound_alias_count"] = alias_res.get("total", 0)
42
+ except Exception:
43
+ pass
44
+ except Exception as exc:
45
+ log.debug("opnsense grains failed: %s", exc)
46
+ return grains
@@ -0,0 +1,171 @@
1
+ import logging
2
+
3
+ log = logging.getLogger(__name__)
4
+
5
+ __virtualname__ = "opnsense_acmeclient"
6
+
7
+
8
+ def __virtual__():
9
+ if "opnsense.search" in __salt__ or "opnsense.call" in __salt__:
10
+ return __virtualname__
11
+ return (False, "opnsense execution module not loaded")
12
+
13
+
14
+ def _search(controller, type_name, search_phrase="", row_count=-1):
15
+ try:
16
+ fn = __salt__["opnsense.search"]
17
+ res = fn("acmeclient", controller, type_name, search_phrase=search_phrase, row_count=row_count)
18
+ if isinstance(res, dict):
19
+ return res.get("rows", [])
20
+ return []
21
+ except Exception as exc:
22
+ log.debug("acmeclient search %s/%s failed: %s", controller, type_name, exc)
23
+ return []
24
+
25
+
26
+ def _sorted_dict(d):
27
+ return dict(sorted(d.items()))
28
+
29
+
30
+ def list_accounts():
31
+ rows = _search("accounts", "account", row_count=-1)
32
+ result = {}
33
+ for r in rows:
34
+ name = r.get("name") or r.get("uuid") or ""
35
+ if not name:
36
+ continue
37
+ result[name] = {
38
+ "name": name,
39
+ "uuid": r.get("uuid"),
40
+ "email": r.get("email") or r.get("contact") or "",
41
+ "ca": r.get("ca") or r.get("directoryUrl") or "",
42
+ "enabled": r.get("enabled") in ("1", True, 1, None),
43
+ "raw": r,
44
+ }
45
+ return _sorted_dict(result)
46
+
47
+
48
+ def list_accounts_pretty():
49
+ data = list_accounts()
50
+ lines = []
51
+ for name in sorted(data):
52
+ info = data[name]
53
+ lines.append(f"{name} [{info.get('ca')}] uuid={info.get('uuid','')[:8]}")
54
+ return lines
55
+
56
+
57
+ def list_validations():
58
+ rows = _search("validations", "validation", row_count=-1)
59
+ result = {}
60
+ for r in rows:
61
+ name = r.get("name") or r.get("uuid") or ""
62
+ if not name:
63
+ continue
64
+ result[name] = {
65
+ "name": name,
66
+ "uuid": r.get("uuid"),
67
+ "method": r.get("method") or r.get("validation_type") or "",
68
+ "enabled": r.get("enabled") in ("1", True, 1, None),
69
+ "raw": r,
70
+ }
71
+ return _sorted_dict(result)
72
+
73
+
74
+ def list_validations_pretty():
75
+ data = list_validations()
76
+ lines = []
77
+ for name in sorted(data):
78
+ info = data[name]
79
+ lines.append(f"{name} method={info.get('method')} uuid={info.get('uuid','')[:8]}")
80
+ return lines
81
+
82
+
83
+ def list_actions():
84
+ rows = _search("actions", "action", row_count=-1)
85
+ result = {}
86
+ for r in rows:
87
+ name = r.get("name") or r.get("uuid") or ""
88
+ if not name:
89
+ continue
90
+ result[name] = {
91
+ "name": name,
92
+ "uuid": r.get("uuid"),
93
+ "type": r.get("type") or r.get("action_type") or "",
94
+ "enabled": r.get("enabled") in ("1", True, 1, None),
95
+ "raw": r,
96
+ }
97
+ return _sorted_dict(result)
98
+
99
+
100
+ def list_actions_pretty():
101
+ data = list_actions()
102
+ lines = []
103
+ for name in sorted(data):
104
+ info = data[name]
105
+ lines.append(f"{name} type={info.get('type')} uuid={info.get('uuid','')[:8]}")
106
+ return lines
107
+
108
+
109
+ def list_certificates():
110
+ rows = _search("certificates", "certificate", row_count=-1)
111
+ result = {}
112
+ for r in rows:
113
+ name = r.get("name") or r.get("common_name") or r.get("uuid") or ""
114
+ if not name:
115
+ continue
116
+ result[name] = {
117
+ "name": name,
118
+ "uuid": r.get("uuid"),
119
+ "common_name": r.get("common_name") or name,
120
+ "status": r.get("status") or r.get("last_status") or "",
121
+ "account": r.get("account") or "",
122
+ "enabled": r.get("enabled") in ("1", True, 1, None),
123
+ "raw": r,
124
+ }
125
+ return _sorted_dict(result)
126
+
127
+
128
+ def list_certificates_simple():
129
+ return {k: v["status"] for k, v in list_certificates().items()}
130
+
131
+
132
+ def list_certificates_pretty():
133
+ data = list_certificates()
134
+ lines = []
135
+ for name in sorted(data):
136
+ info = data[name]
137
+ lines.append(f"{name} status={info.get('status')} uuid={info.get('uuid','')[:8]}")
138
+ return lines
139
+
140
+
141
+ def list_settings():
142
+ try:
143
+ res = __salt__["opnsense.call"]("acmeclient", "settings", "get", method="GET")
144
+ return res
145
+ except Exception as exc:
146
+ log.debug("acme settings get failed: %s", exc)
147
+ return {}
148
+
149
+
150
+ def get_account(name):
151
+ accounts = list_accounts()
152
+ return accounts.get(name)
153
+
154
+
155
+ def get_validation(name):
156
+ vals = list_validations()
157
+ return vals.get(name)
158
+
159
+
160
+ def get_certificate(name):
161
+ certs = list_certificates()
162
+ return certs.get(name)
163
+
164
+
165
+ def service_status():
166
+ try:
167
+ res = __salt__["opnsense.call"]("acmeclient", "service", "status", method="POST", data={})
168
+ return res
169
+ except Exception as exc:
170
+ log.debug("acme service status failed: %s", exc)
171
+ return {"error": str(exc)}
@@ -0,0 +1,229 @@
1
+ import logging
2
+
3
+ log = logging.getLogger(__name__)
4
+
5
+ __virtualname__ = "opnsense_bind"
6
+
7
+
8
+ def __virtual__():
9
+ if "opnsense.search" in __salt__ or "opnsense.call" in __salt__:
10
+ return __virtualname__
11
+ return (False, "opnsense execution module not loaded")
12
+
13
+
14
+ def _search(controller, type_name, search_phrase="", row_count=-1):
15
+ try:
16
+ fn = __salt__["opnsense.search"]
17
+ res = fn("bind", controller, type_name, search_phrase=search_phrase, row_count=row_count)
18
+ if isinstance(res, dict):
19
+ return res.get("rows", [])
20
+ return []
21
+ except Exception as exc:
22
+ log.debug("bind search %s/%s failed: %s", controller, type_name, exc)
23
+ return []
24
+
25
+
26
+ def _search_domain_all():
27
+ all_rows = []
28
+ for c in ("domain",):
29
+ for t in ("primary_domain", "secondary_domain", "forward_domain", "master_domain", "slave_domain", "domain"):
30
+ try:
31
+ rows = _search(c, t, row_count=-1)
32
+ if rows:
33
+ for r in rows:
34
+ r["_bind_type"] = t
35
+ r["_bind_controller"] = c
36
+ all_rows.extend(rows)
37
+ except Exception:
38
+ continue
39
+ dedup = {}
40
+ for r in all_rows:
41
+ uuid = r.get("uuid")
42
+ if uuid and uuid not in dedup:
43
+ dedup[uuid] = r
44
+ elif not uuid:
45
+ key = r.get("domainname") or r.get("domain") or r.get("name")
46
+ if key and key not in dedup:
47
+ dedup[key] = r
48
+ return list(dedup.values())
49
+
50
+
51
+ def list_domains(domain_type=None):
52
+ rows = _search_domain_all()
53
+ result = {}
54
+ for r in rows:
55
+ dname = r.get("domainname") or r.get("domain") or r.get("name") or ""
56
+ if not dname:
57
+ continue
58
+ btype = r.get("_bind_type", "primary_domain")
59
+ if domain_type and domain_type not in btype and domain_type != "all":
60
+ if domain_type == "primary" and "primary" not in btype:
61
+ continue
62
+ if domain_type == "secondary" and "secondary" not in btype:
63
+ continue
64
+ if domain_type == "forward" and "forward" not in btype:
65
+ continue
66
+ result[dname] = {
67
+ "domainname": dname,
68
+ "type": btype,
69
+ "uuid": r.get("uuid"),
70
+ "description": r.get("description", ""),
71
+ "enabled": r.get("enabled") in ("1", True, 1, None),
72
+ "raw": r,
73
+ }
74
+ return dict(sorted(result.items()))
75
+
76
+
77
+ def list_domains_simple():
78
+ return {k: v["uuid"] for k, v in list_domains().items()}
79
+
80
+
81
+ def list_domains_pretty(domain_type=None):
82
+ data = list_domains(domain_type=domain_type)
83
+ lines = []
84
+ for dname in sorted(data):
85
+ info = data[dname]
86
+ en = "enabled" if info.get("enabled") else "disabled"
87
+ lines.append(f"{dname} [{info.get('type')}] ({en}) {info.get('uuid','')[:8]}")
88
+ return lines
89
+
90
+
91
+ def _resolve_domain_uuid(domain):
92
+ if not domain:
93
+ return None
94
+ try:
95
+ from saltext.opnsense.utils.common import is_uuid
96
+ except Exception:
97
+ import re
98
+
99
+ def is_uuid(v):
100
+ return bool(re.match(r"^[0-9a-fA-F]{8}-", str(v)))
101
+
102
+ if is_uuid(domain):
103
+ return domain
104
+ domains = list_domains()
105
+ if domain in domains:
106
+ return domains[domain]["uuid"]
107
+ for rows in [_search_domain_all()]:
108
+ for r in rows:
109
+ if r.get("domainname") == domain:
110
+ return r.get("uuid")
111
+ return None
112
+
113
+
114
+ def list_records(domain=None, domain_filter=None, record_type=None, name=None):
115
+ if domain is None and domain_filter is not None:
116
+ domain = domain_filter
117
+ rows = _search("record", "record", row_count=-1)
118
+ domain_uuid_filter = None
119
+ domain_name_filter = None
120
+ if domain:
121
+ domain_uuid_filter = _resolve_domain_uuid(domain)
122
+ if domain_uuid_filter:
123
+ domains_map = {v["uuid"]: k for k, v in list_domains().items() if v.get("uuid")}
124
+ domain_name_filter = domains_map.get(domain_uuid_filter, domain)
125
+ if domain_name_filter is None or domain_name_filter == domain_uuid_filter:
126
+ domain_name_filter = domain if "." in str(domain) else None
127
+ else:
128
+ domain_uuid_filter = domain if domain and len(domain) > 20 else None
129
+ domain_name_filter = domain if "." in str(domain) else None
130
+
131
+ result = {}
132
+ for r in rows:
133
+ r_domain = r.get("domain") or r.get("domain_uuid") or ""
134
+ r_name = r.get("name") or ""
135
+ r_type = r.get("type") or ""
136
+ r_value = r.get("value") or r.get("server") or ""
137
+
138
+ if domain_uuid_filter:
139
+ if r_domain != domain_uuid_filter and r_domain != domain:
140
+ if domain_uuid_filter and r_domain != domain_uuid_filter:
141
+ continue
142
+ if record_type and r_type != record_type:
143
+ continue
144
+ if name and r_name != name:
145
+ continue
146
+
147
+ key = f"{r_name}.{domain_name_filter or r_domain}" if r_name else r.get("uuid", "")
148
+ if not key:
149
+ key = r.get("uuid", "")
150
+
151
+ result[key] = {
152
+ "name": r_name,
153
+ "domain": domain_name_filter or r_domain,
154
+ "domain_uuid": r_domain,
155
+ "type": r_type,
156
+ "value": r_value,
157
+ "ttl": r.get("ttl", ""),
158
+ "uuid": r.get("uuid"),
159
+ "description": r.get("description", ""),
160
+ "enabled": r.get("enabled") in ("1", True, 1, None),
161
+ "raw": r,
162
+ }
163
+ return dict(sorted(result.items()))
164
+
165
+
166
+ def list_records_simple(domain=None, domain_filter=None):
167
+ if domain is None and domain_filter is not None:
168
+ domain = domain_filter
169
+ return list_records(domain=domain)
170
+
171
+
172
+ def list_records_pretty(domain=None, domain_filter=None, record_type=None):
173
+ if domain is None and domain_filter is not None:
174
+ domain = domain_filter
175
+ data = list_records(domain=domain, record_type=record_type)
176
+ lines = []
177
+ for key in sorted(data):
178
+ info = data[key]
179
+ lines.append(f"{info.get('name')} {info.get('type')} {info.get('value')}")
180
+ return lines
181
+
182
+
183
+ def list_records_as_strings(domain=None, domain_filter=None):
184
+ return list_records_pretty(domain=domain, domain_filter=domain_filter)
185
+
186
+
187
+ def list_acls():
188
+ rows = _search("acl", "acl", row_count=-1)
189
+ result = {}
190
+ for r in rows:
191
+ name = r.get("name") or r.get("uuid") or ""
192
+ result[name] = {
193
+ "name": name,
194
+ "uuid": r.get("uuid"),
195
+ "description": r.get("description", ""),
196
+ "enabled": r.get("enabled") in ("1", True, 1),
197
+ "raw": r,
198
+ }
199
+ return dict(sorted(result.items()))
200
+
201
+
202
+ def list_acls_pretty():
203
+ data = list_acls()
204
+ lines = []
205
+ for name in sorted(data):
206
+ info = data[name]
207
+ lines.append(f"{name} ({'enabled' if info.get('enabled') else 'disabled'}) {info.get('uuid','')[:8]}")
208
+ return lines
209
+
210
+
211
+ def get_domain(name):
212
+ domains = list_domains()
213
+ return domains.get(name)
214
+
215
+
216
+ def get_record(name, domain=None):
217
+ records = list_records(domain=domain, name=name)
218
+ for k, v in records.items():
219
+ if v["name"] == name:
220
+ return v
221
+ return None
222
+
223
+
224
+ def resolve_domain(name_or_uuid):
225
+ return _resolve_domain_uuid(name_or_uuid)
226
+
227
+
228
+ def get_domain_uuid(name_or_uuid):
229
+ return _resolve_domain_uuid(name_or_uuid)
@@ -0,0 +1,181 @@
1
+ import logging
2
+
3
+ log = logging.getLogger(__name__)
4
+
5
+ __virtualname__ = "opnsense_dns"
6
+
7
+
8
+ def __virtual__():
9
+ if "opnsense.search" in __salt__ or "opnsense_unbound.list_aliases" in __salt__:
10
+ return __virtualname__
11
+ if "opnsense.search" in __salt__:
12
+ return __virtualname__
13
+ return (False, "opnsense execution module not loaded")
14
+
15
+
16
+ def _pillar_aliases():
17
+ try:
18
+ pillar = __pillar__ or {}
19
+ op = pillar.get("opnsense", {}) if isinstance(pillar, dict) else {}
20
+ return op.get("aliases", {}), op.get("purge_aliases", {}), op.get("cluster_parent", {})
21
+ except Exception:
22
+ return {}, {}, {}
23
+
24
+
25
+ def list_aliases(domain=None, parent=None):
26
+ """
27
+ List DNS aliases as dict fqdn -> info.
28
+
29
+ Delightful alternative to raw search. Returns sorted dict keyed by FQDN.
30
+
31
+ Example:
32
+ salt opnsense-router opnsense_dns.list_aliases
33
+ salt opnsense-router opnsense_dns.list_aliases domain=example.com
34
+ salt opnsense-router opnsense_dns.list_aliases parent=cluster.example.com
35
+ """
36
+ try:
37
+ fn = __salt__["opnsense_unbound.list_aliases"]
38
+ return fn(domain=domain, parent=parent)
39
+ except Exception:
40
+ try:
41
+ fn = __salt__["opnsense.search"]
42
+ res = fn("unbound", "settings", "host_alias", row_count=-1)
43
+ rows = res.get("rows", []) if isinstance(res, dict) else []
44
+ result = {}
45
+ for r in rows:
46
+ hn = r.get("hostname")
47
+ dom = r.get("domain")
48
+ if not hn or not dom:
49
+ continue
50
+ if domain and dom != domain:
51
+ continue
52
+ fqdn = f"{hn}.{dom}"
53
+ result[fqdn] = r.get("host")
54
+ return result
55
+ except Exception as exc:
56
+ log.debug("dns list_aliases failed: %s", exc)
57
+ return {}
58
+
59
+
60
+ def list_aliases_detailed(domain=None, parent=None):
61
+ """
62
+ Alias to list_aliases returning full info dict.
63
+
64
+ Example:
65
+ salt opnsense-router opnsense_dns.list_aliases_detailed
66
+ """
67
+ try:
68
+ fn = __salt__["opnsense_unbound.list_aliases"]
69
+ return fn(domain=domain, parent=parent)
70
+ except Exception:
71
+ return list_aliases(domain=domain, parent=parent)
72
+
73
+
74
+ def list_aliases_simple(domain=None, parent=None):
75
+ """
76
+ Simple mapping fqdn -> parent FQDN. Great for CLI --out=table.
77
+
78
+ Example:
79
+ salt opnsense-router opnsense_dns.list_aliases_simple --out=table
80
+ salt opnsense-router opnsense_dns.list_aliases_simple domain=example.com
81
+ """
82
+ try:
83
+ fn = __salt__["opnsense_unbound.list_aliases_simple"]
84
+ return fn(domain=domain, parent=parent)
85
+ except Exception:
86
+ try:
87
+ fn = __salt__["opnsense_unbound.list_aliases"]
88
+ data = fn(domain=domain, parent=parent)
89
+ return {fqdn: info.get("parent") if isinstance(info, dict) else info for fqdn, info in data.items()}
90
+ except Exception:
91
+ data = list_aliases(domain=domain, parent=parent)
92
+ return {fqdn: info.get("parent") if isinstance(info, dict) else str(info) for fqdn, info in data.items()}
93
+
94
+
95
+ def list_aliases_pretty(domain=None, parent=None):
96
+ """
97
+ Pretty list of "fqdn -> parent (enabled)" for human output. Works with --out=table.
98
+
99
+ Example:
100
+ salt opnsense-router opnsense_dns.list_aliases_pretty --out=table
101
+ salt opnsense-router opnsense_dns.list_aliases_pretty domain=example.com --out=table
102
+ """
103
+ try:
104
+ fn = __salt__["opnsense_unbound.list_aliases_pretty"]
105
+ return fn(domain=domain, parent=parent)
106
+ except Exception:
107
+ pass
108
+ try:
109
+ fn = __salt__["opnsense_unbound.list_aliases"]
110
+ data = fn(domain=domain, parent=parent)
111
+ except Exception:
112
+ data = list_aliases(domain=domain, parent=parent)
113
+ lines = []
114
+ for fqdn in sorted(data):
115
+ info = data[fqdn]
116
+ if isinstance(info, dict):
117
+ parent_f = info.get("parent", "")
118
+ en = "enabled" if info.get("enabled") else "disabled" if "enabled" in info else "enabled"
119
+ lines.append(f"{fqdn} -> {parent_f} ({en})")
120
+ else:
121
+ lines.append(f"{fqdn} -> {info}")
122
+ return lines
123
+
124
+
125
+ def list_aliases_simple_pretty(domain=None, parent=None):
126
+ """
127
+ Alias to list_aliases_pretty for backward compat.
128
+ """
129
+ return list_aliases_pretty(domain=domain, parent=parent)
130
+
131
+
132
+ def list_pillar_aliases():
133
+ aliases, purge, parent = _pillar_aliases()
134
+ return {
135
+ "aliases": aliases,
136
+ "purge_aliases": purge,
137
+ "cluster_parent": parent,
138
+ }
139
+
140
+
141
+ def resolve_parent(parent=None):
142
+ if parent:
143
+ try:
144
+ fn = __salt__["opnsense_unbound.resolve_parent"]
145
+ return fn(parent)
146
+ except Exception:
147
+ return None
148
+ _, _, cp = _pillar_aliases()
149
+ if isinstance(cp, dict) and cp.get("uuid"):
150
+ return cp["uuid"]
151
+ if isinstance(cp, dict) and cp.get("hostname"):
152
+ try:
153
+ fn = __salt__["opnsense_unbound.resolve_parent"]
154
+ fqdn = f"{cp['hostname']}.{cp.get('domain','example.com')}"
155
+ return fn(fqdn)
156
+ except Exception:
157
+ return None
158
+ return None
159
+
160
+
161
+ def managed_preview(parent=None, aliases=None, purge=None):
162
+ aliases_p, purge_p, parent_p = _pillar_aliases()
163
+ if aliases is None:
164
+ aliases = aliases_p
165
+ if purge is None:
166
+ purge = purge_p
167
+ if parent is None:
168
+ if isinstance(parent_p, dict) and parent_p.get("hostname"):
169
+ parent = f"{parent_p['hostname']}.{parent_p.get('domain','example.com')}"
170
+ else:
171
+ parent = parent_p
172
+ live = list_aliases()
173
+ desired_fqdns = {f"{h}.{dom}" for dom, hosts in (aliases or {}).items() for h in (hosts or [])}
174
+ purge_fqdns = {f"{h}.{dom}" for dom, hosts in (purge or {}).items() for h in (hosts or [])}
175
+ return {
176
+ "parent": parent,
177
+ "desired": sorted(desired_fqdns),
178
+ "purge": sorted(purge_fqdns),
179
+ "live_count": len(live),
180
+ "live": live,
181
+ }