gowalk-cicd 1.0.151 → 1.0.152
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.
- package/action/.daemux-version +1 -1
- package/android-action/.daemux-version +1 -1
- package/backend-action/.daemux-version +1 -1
- package/backend-action/network_pools.py +22 -10
- package/backend-action/test_network_rule_diagnostics.py +53 -0
- package/docs/BACKEND_NETWORKS.md +3 -0
- package/package.json +1 -1
package/action/.daemux-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.152
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.152
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.152
|
|
@@ -10,7 +10,8 @@ FAILURES = frozenset({
|
|
|
10
10
|
'pool_provenance_unknown', 'pool_inventory_invalid', 'host_routes_unsupported',
|
|
11
11
|
'host_rules_unsupported', 'host_addresses_invalid', 'resolver_inventory_invalid',
|
|
12
12
|
'resolver_upstream_unknown',
|
|
13
|
-
})
|
|
13
|
+
}) | frozenset('host_rules_unsupported_' + part for part in
|
|
14
|
+
('count', 'fields', 'priority', 'source', 'table', 'structure', 'fwmark', 'fwmask', 'action', 'duplicate'))
|
|
14
15
|
PRIVATE = tuple(ipaddress.ip_network(value) for value in ('10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'))
|
|
15
16
|
DEVICE = re.compile(r'[A-Za-z0-9][A-Za-z0-9_.:-]{0,14}')
|
|
16
17
|
|
|
@@ -195,7 +196,7 @@ def validate_rules(rows):
|
|
|
195
196
|
"""Admit ordinary Linux rules or the exact Tailscale v1.88.2 baseIPRules chain."""
|
|
196
197
|
# https://github.com/tailscale/tailscale/blob/v1.88.2/wgengine/router/router_linux.go
|
|
197
198
|
if not isinstance(rows, list) or len(rows) not in (3, 7):
|
|
198
|
-
raise InventoryFailure('
|
|
199
|
+
raise InventoryFailure('host_rules_unsupported_count')
|
|
199
200
|
expected = [{'priority': p, 'src': 'all', 'table': t}
|
|
200
201
|
for p, t in ((0, 'local'), (32766, 'main'), (32767, 'default'))]
|
|
201
202
|
if len(rows) == 7:
|
|
@@ -205,21 +206,32 @@ def validate_rules(rows):
|
|
|
205
206
|
actual = []
|
|
206
207
|
tables = {255: 'local', 254: 'main', 253: 'default'}
|
|
207
208
|
for row in rows:
|
|
208
|
-
if
|
|
209
|
-
'fwmark', 'fwmask', 'action'}
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
if not isinstance(row, dict) or set(row) - {'priority', 'src', 'table', 'protocol',
|
|
210
|
+
'fwmark', 'fwmask', 'action'}:
|
|
211
|
+
raise InventoryFailure('host_rules_unsupported_fields')
|
|
212
|
+
if type(row.get('priority')) is not int:
|
|
213
|
+
raise InventoryFailure('host_rules_unsupported_priority')
|
|
214
|
+
if row.get('src') != 'all':
|
|
215
|
+
raise InventoryFailure('host_rules_unsupported_source')
|
|
212
216
|
normalized = {key: value for key, value in row.items() if key != 'protocol'}
|
|
213
217
|
table = normalized.get('table')
|
|
214
218
|
if table is not None:
|
|
215
219
|
if type(table) not in (str, int):
|
|
216
|
-
raise InventoryFailure('
|
|
220
|
+
raise InventoryFailure('host_rules_unsupported_table')
|
|
217
221
|
normalized['table'] = tables.get(table, table)
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
wanted = next((item for item in expected if item['priority'] == normalized['priority']), None)
|
|
223
|
+
if wanted is None:
|
|
224
|
+
raise InventoryFailure('host_rules_unsupported_priority')
|
|
225
|
+
if set(normalized) != set(wanted):
|
|
226
|
+
raise InventoryFailure('host_rules_unsupported_structure')
|
|
227
|
+
for field in ('table', 'fwmark', 'fwmask', 'action'):
|
|
228
|
+
if normalized.get(field) != wanted.get(field):
|
|
229
|
+
raise InventoryFailure('host_rules_unsupported_' + field)
|
|
230
|
+
if normalized in actual:
|
|
231
|
+
raise InventoryFailure('host_rules_unsupported_duplicate')
|
|
220
232
|
actual.append(normalized)
|
|
221
233
|
if len(actual) != len(expected):
|
|
222
|
-
raise InventoryFailure('
|
|
234
|
+
raise InventoryFailure('host_rules_unsupported_count')
|
|
223
235
|
|
|
224
236
|
|
|
225
237
|
def resolver_exclusions(raw, *, resolved=False, records=None):
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Classify refused policy shapes without publishing their private values or relaxing admission."""
|
|
2
|
+
import copy
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
from network_diagnostics import SCHEMA, decode
|
|
9
|
+
from network_inventory import collect
|
|
10
|
+
from network_pools import InventoryFailure, validate_rules
|
|
11
|
+
from test_network_inventory import FakeHost, RULES
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RuleDiagnosticTests(unittest.TestCase):
|
|
15
|
+
def test_each_rejected_shape_keeps_a_closed_reason_without_values(self):
|
|
16
|
+
marked = {'src': 'all', 'fwmark': '0x80000', 'fwmask': '0xff0000'}
|
|
17
|
+
valid = RULES + [dict(marked, priority=5210, table='main'), dict(marked, priority=5230, table='default'),
|
|
18
|
+
dict(marked, priority=5250, action='unreachable'),
|
|
19
|
+
{'priority': 5270, 'src': 'all', 'table': 52}]
|
|
20
|
+
validate_rules(valid)
|
|
21
|
+
cases = [('fields', 'PRIVATE_SENTINEL', 'PRIVATE_SENTINEL'), ('priority', 'priority', True),
|
|
22
|
+
('source', 'src', 'PRIVATE_SENTINEL'), ('table', 'table', 'PRIVATE_SENTINEL'),
|
|
23
|
+
('fwmark', 'fwmark', 'PRIVATE_SENTINEL'), ('fwmask', 'fwmask', 'PRIVATE_SENTINEL')]
|
|
24
|
+
for reason, key, value in cases:
|
|
25
|
+
rows = copy.deepcopy(valid)
|
|
26
|
+
rows[3][key] = value
|
|
27
|
+
with self.subTest(reason=reason), self.assertRaises(InventoryFailure) as caught:
|
|
28
|
+
validate_rules(rows)
|
|
29
|
+
self.assertEqual(caught.exception.code, 'host_rules_unsupported_' + reason)
|
|
30
|
+
self.assertNotIn('PRIVATE_SENTINEL', str(caught.exception))
|
|
31
|
+
for rows, reason in ((valid[:-1], 'count'), (valid[:-1] + [valid[0]], 'duplicate'),
|
|
32
|
+
([dict(row, fwmark='0x80000') for row in RULES], 'structure')):
|
|
33
|
+
with self.subTest(reason=reason), self.assertRaises(InventoryFailure) as caught:
|
|
34
|
+
validate_rules(rows)
|
|
35
|
+
self.assertEqual(caught.exception.code, 'host_rules_unsupported_' + reason)
|
|
36
|
+
|
|
37
|
+
def test_scoped_diagnostic_retains_reason_and_partial_metadata_only(self):
|
|
38
|
+
host = FakeHost()
|
|
39
|
+
host.answers[('ip', '-j', '-4', 'rule', 'show')][0]['src'] = 'PRIVATE_SENTINEL'
|
|
40
|
+
with patch.dict(os.environ, {}, clear=True), self.assertRaises(InventoryFailure) as caught:
|
|
41
|
+
collect(host, require_pools=False)
|
|
42
|
+
failure = caught.exception
|
|
43
|
+
result = dict(failure.details, schema=SCHEMA, ok=False, project='fixture', failure=failure.code)
|
|
44
|
+
raw = json.dumps(result).encode()
|
|
45
|
+
self.assertEqual(decode(1, raw, 'fixture')['failure'], 'host_rules_unsupported_source')
|
|
46
|
+
self.assertFalse(result['complete'])
|
|
47
|
+
self.assertNotIn(b'PRIVATE_SENTINEL', raw)
|
|
48
|
+
self.assertNotIn('networks', result)
|
|
49
|
+
self.assertFalse(any('create' in call or 'rm' in call for call in host.calls))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == '__main__':
|
|
53
|
+
unittest.main()
|
package/docs/BACKEND_NETWORKS.md
CHANGED
|
@@ -79,6 +79,9 @@ A routing refusal can retain safe engine/pool metadata with `complete:false` and
|
|
|
79
79
|
partial evidence never admits allocation. A successful diagnostic is a readback, not a deployment,
|
|
80
80
|
capacity reservation, proof of application health or proof that another project cannot reach the app.
|
|
81
81
|
Raw container data, labels, environment, daemon configuration and command errors are not exported.
|
|
82
|
+
Policy-rule refusals use `host_rules_unsupported_<reason>` to distinguish count, fields, priority,
|
|
83
|
+
source, table, structure, fwmark, fwmask, action and duplicate mismatches. These fixed classifications
|
|
84
|
+
retain the strict supported-rule comparison and publish no rejected keys or values.
|
|
82
85
|
|
|
83
86
|
The deployment emits its own `gowalk-cicd/backend-networks.v1` receipt. The existing
|
|
84
87
|
`backend_deploy_failed` phase remains `compose_up` on a preparation refusal. Normal public health,
|