Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4676d17aea | ||
|
|
52e6a987cc |
No files matched your search
@@ -115,6 +115,9 @@ All notable changes to Housecarl are documented here. The format follows
|
|||||||
ruleset, for entries made before this version too: the configuration
|
ruleset, for entries made before this version too: the configuration
|
||||||
before and after it is got back from the current one and rendered by
|
before and after it is got back from the current one and rendered by
|
||||||
this version.
|
this version.
|
||||||
|
- Negating a match is said first: the zone, From and To fields (rules
|
||||||
|
and NAT) start with "any of" / "any but" instead of a checkbox under
|
||||||
|
the list, and an "any but" list is tinted red.
|
||||||
- The rule dialog shows only the zone fields its chain can use: no
|
- The rule dialog shows only the zone fields its chain can use: no
|
||||||
"Going out on" for input and early, no "Coming in on" for output
|
"Going out on" for input and early, no "Coming in on" for output
|
||||||
(they could only be refused). A zone left in a field the chain hides
|
(they could only be refused). A zone left in a field the chain hides
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ The web UI is on `http://127.0.0.1:8070/` (see `listen` in
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Housecarl 0.2.0 is in testing (0.2.0-rc.3): zones, address sets and
|
Housecarl 0.2.0 is in testing (0.2.0-rc.3+52e6a98): zones, address sets and
|
||||||
services with ordered rules, IPv4 and IPv6, port forwards with hairpin NAT
|
services with ordered rules, IPv4 and IPv6, port forwards with hairpin NAT
|
||||||
and IPv6 pinholes, source NAT and redirects, built-ins, probation for
|
and IPv6 pinholes, source NAT and redirects, built-ins, probation for
|
||||||
network changes (confirmed by the client that made them), the ruleset
|
network changes (confirmed by the client that made them), the ruleset
|
||||||
|
|||||||
@@ -379,6 +379,10 @@ pre.wrap { white-space: pre-wrap; word-break: break-all; font-size: 12px; }
|
|||||||
.list-rows { display: flex; flex-direction: column; gap: 6px; margin-bottom: 6px; }
|
.list-rows { display: flex; flex-direction: column; gap: 6px; margin-bottom: 6px; }
|
||||||
.list-row { display: flex; gap: 6px; align-items: center; }
|
.list-row { display: flex; gap: 6px; align-items: center; }
|
||||||
.list-row input { width: 26em; max-width: 100%; }
|
.list-row input { width: 26em; max-width: 100%; }
|
||||||
|
.negatable { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; }
|
||||||
|
.negatable > .list-editor { flex: 1; min-width: 0; }
|
||||||
|
.negatable.negated > select { color: var(--err); border-color: var(--err); font-weight: 600; }
|
||||||
|
.negatable.negated .list-row input { background: var(--err-soft); color: var(--del-text); border-color: var(--err); }
|
||||||
input.short { width: 7em; }
|
input.short { width: 7em; }
|
||||||
.builtins { margin-bottom: 10px; line-height: 1.6; }
|
.builtins { margin-bottom: 10px; line-height: 1.6; }
|
||||||
.policy { margin-top: 10px; }
|
.policy { margin-top: 10px; }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Widgets shared by several pages: list editors with suggestions, the
|
// Widgets shared by several pages: list editors with suggestions, the
|
||||||
// match editor of rules and NAT, descriptions, hit counts, durations.
|
// match editor of rules and NAT, descriptions, hit counts, durations.
|
||||||
import { h, input, select, field, checkbox } from "./ui.js";
|
import { h, input, select, field } from "./ui.js";
|
||||||
import { get, api } from "./api.js";
|
import { get, api } from "./api.js";
|
||||||
|
|
||||||
// ---- durations
|
// ---- durations
|
||||||
@@ -65,34 +65,44 @@ export function listEditor(values, opts = {}) {
|
|||||||
|
|
||||||
// ---- matches
|
// ---- matches
|
||||||
|
|
||||||
|
// negatable puts "any of" / "any but" in front of a list editor, so a
|
||||||
|
// field says first whether it matches its entries or everything else;
|
||||||
|
// "any but" tints the list. value() is the list, negated() the choice.
|
||||||
|
function negatable(list, not) {
|
||||||
|
const mode = select([["", "any of"], ["not", "any but"]], not ? "not" : "", { class: "inline", "aria-label": "match these, or everything but these" });
|
||||||
|
const box = h("div", { class: "negatable" + (not ? " negated" : "") }, mode, list);
|
||||||
|
mode.addEventListener("change", () => box.classList.toggle("negated", mode.value === "not"));
|
||||||
|
box.value = list.value;
|
||||||
|
box.negated = () => mode.value === "not";
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
// matchEditor edits what a rule, source NAT or redirect matches. It
|
// matchEditor edits what a rule, source NAT or redirect matches. It
|
||||||
// returns the form-grid fields; value() is the match's fields.
|
// returns the form-grid fields; value() is the match's fields.
|
||||||
export function matchEditor(m, n, opts = {}) {
|
export function matchEditor(m, n, opts = {}) {
|
||||||
m = m || {};
|
m = m || {};
|
||||||
const zones = (list) => listEditor(list, { suggestions: n.zones, placeholder: "zone", mono: false, addLabel: "Add zone" });
|
const zones = (list) => listEditor(list, { suggestions: n.zones, placeholder: "zone", mono: false, addLabel: "Add zone" });
|
||||||
const addrs = (list) => listEditor(list, { suggestions: n.sets, placeholder: "a set, 192.168.1.0/24, 2001:db8:1::/64, 192.168.1.100-192.168.1.199", addLabel: "Add" });
|
const addrs = (list) => listEditor(list, { suggestions: n.sets, placeholder: "a set, 192.168.1.0/24, 2001:db8:1::/64, 192.168.1.100-192.168.1.199", addLabel: "Add" });
|
||||||
const inZ = zones(m.in_zones), outZ = zones(m.out_zones);
|
const inZ = negatable(zones(m.in_zones), m.not_in), outZ = negatable(zones(m.out_zones), m.not_out);
|
||||||
const notIn = checkbox("any zone but these", m.not_in), notOut = checkbox("any zone but these", m.not_out);
|
const src = negatable(addrs(m.sources), m.not_sources), dst = negatable(addrs(m.destinations), m.not_destinations);
|
||||||
const src = addrs(m.sources), dst = addrs(m.destinations);
|
|
||||||
const notSrc = checkbox("any source but these", m.not_sources), notDst = checkbox("any destination but these", m.not_destinations);
|
|
||||||
const svc = listEditor(m.services, { suggestions: n.services, placeholder: "ssh, https, tcp/8080, udp/1000-2000, icmp/echo-request", addLabel: "Add service" });
|
const svc = listEditor(m.services, { suggestions: n.services, placeholder: "ssh, https, tcp/8080, udp/1000-2000, icmp/echo-request", addLabel: "Add service" });
|
||||||
const fam = select([["", "IPv4 and IPv6"], ["ipv4", "IPv4 only"], ["ipv6", "IPv6 only"]], m.family || "");
|
const fam = select([["", "IPv4 and IPv6"], ["ipv4", "IPv4 only"], ["ipv6", "IPv6 only"]], m.family || "");
|
||||||
const inField = opts.in !== false ? field("Coming in on", h("div", null, inZ, notIn)) : null;
|
const inField = opts.in !== false ? field("Coming in on", inZ) : null;
|
||||||
const outField = opts.out !== false ? field("Going out on", h("div", null, outZ, notOut)) : null;
|
const outField = opts.out !== false ? field("Going out on", outZ) : null;
|
||||||
const fields = [
|
const fields = [
|
||||||
inField,
|
inField,
|
||||||
outField,
|
outField,
|
||||||
field("From", h("div", null, src, notSrc), "Sets, addresses, prefixes or ranges, IPv4 and IPv6 mixed. Empty: any."),
|
field("From", src, "Sets, addresses, prefixes or ranges, IPv4 and IPv6 mixed. Empty: any."),
|
||||||
field("To", h("div", null, dst, notDst)),
|
field("To", dst),
|
||||||
field("Services", svc, "Named services (built in, or yours) or entries such as tcp/8080. Empty: any traffic."),
|
field("Services", svc, "Named services (built in, or yours) or entries such as tcp/8080. Empty: any traffic."),
|
||||||
field("Family", fam),
|
field("Family", fam),
|
||||||
];
|
];
|
||||||
fields.value = () => {
|
fields.value = () => {
|
||||||
const v = { sources: src.value(), not_sources: notSrc.input.checked, destinations: dst.value(), not_destinations: notDst.input.checked,
|
const v = { sources: src.value(), not_sources: src.negated(), destinations: dst.value(), not_destinations: dst.negated(),
|
||||||
services: svc.value(), family: fam.value };
|
services: svc.value(), family: fam.value };
|
||||||
// A hidden zone field (the chain has no such interface) matches nothing.
|
// A hidden zone field (the chain has no such interface) matches nothing.
|
||||||
if (inField) Object.assign(v, inField[1].classList.contains("hidden") ? { in_zones: [], not_in: false } : { in_zones: inZ.value(), not_in: notIn.input.checked });
|
if (inField) Object.assign(v, inField[1].classList.contains("hidden") ? { in_zones: [], not_in: false } : { in_zones: inZ.value(), not_in: inZ.negated() });
|
||||||
if (outField) Object.assign(v, outField[1].classList.contains("hidden") ? { out_zones: [], not_out: false } : { out_zones: outZ.value(), not_out: notOut.input.checked });
|
if (outField) Object.assign(v, outField[1].classList.contains("hidden") ? { out_zones: [], not_out: false } : { out_zones: outZ.value(), not_out: outZ.negated() });
|
||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
// zones shows the zone fields a chain can use: input and early have no
|
// zones shows the zone fields a chain can use: input and early have no
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ done
|
|||||||
files="$files dist/SHA256SUMS"
|
files="$files dist/SHA256SUMS"
|
||||||
|
|
||||||
# The release (created, or found when publishing again).
|
# The release (created, or found when publishing again).
|
||||||
release=$(call 404 "$api/releases/tags/$tag")
|
release=$(call 404 "$api/releases/tags/$(printf '%s' "$tag" | sed 's/+/%2B/g')")
|
||||||
id=$(printf '%s\n' "$release" | jq -r '.id // empty')
|
id=$(printf '%s\n' "$release" | jq -r '.id // empty')
|
||||||
if [ -z "$id" ]; then
|
if [ -z "$id" ]; then
|
||||||
body=$(jq -n --arg tag "$tag" --arg notes "$notes" --argjson pre "$prerelease" \
|
body=$(jq -n --arg tag "$tag" --arg notes "$notes" --argjson pre "$prerelease" \
|
||||||
@@ -127,7 +127,8 @@ for f in $files; do
|
|||||||
name=${f#dist/}
|
name=${f#dist/}
|
||||||
old=$(printf '%s\n' "$release" | jq -r --arg n "$name" '.assets[]? | select(.name == $n) | .id')
|
old=$(printf '%s\n' "$release" | jq -r --arg n "$name" '.assets[]? | select(.name == $n) | .id')
|
||||||
[ -z "$old" ] || call "" -X DELETE "$api/releases/$id/assets/$old" >/dev/null
|
[ -z "$old" ] || call "" -X DELETE "$api/releases/$id/assets/$old" >/dev/null
|
||||||
call "" -X POST -F "attachment=@$f" "$api/releases/$id/assets?name=$name" >/dev/null
|
# A + (build metadata, 0.2.0-rc.3+1a2b3c4) would be a space in a query.
|
||||||
|
call "" -X POST -F "attachment=@$f" "$api/releases/$id/assets?name=$(printf '%s' "$name" | sed 's/+/%2B/g')" >/dev/null
|
||||||
echo "gitea-release: attached $name"
|
echo "gitea-release: attached $name"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user