Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46c319eced | ||
|
|
24dbc0a869 |
No files matched your search
@@ -76,6 +76,15 @@ All notable changes to Housecarl are documented here. The format follows
|
||||
- Import: sets with `flags timeout` become dynamic sets; a DNAT of
|
||||
several ports becomes one port forward per port.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Rules read as a direction.** A match's zones are written `lan → wan`,
|
||||
`any but wan → any`, or with the firewall itself as the other end
|
||||
(`lan → firewall` in input, `firewall → wan` in output), instead of
|
||||
`in lan · out wan` and `in not wan`; exclusions read `from any but X`.
|
||||
On the Rules and NAT pages and in `hc`; the flags and JSON stay as
|
||||
they were.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `hc stats top devices` listed each device twice (once for what it
|
||||
|
||||
@@ -123,7 +123,7 @@ The web UI is on `http://127.0.0.1:8070/` (see `listen` in
|
||||
|
||||
## Status
|
||||
|
||||
Housecarl 0.1.1 is in testing (0.1.1-rc.9): zones, address sets and
|
||||
Housecarl 0.1.1 is in testing (0.1.1-rc.10): zones, address sets and
|
||||
services with ordered rules, IPv4 and IPv6, port forwards with hairpin NAT
|
||||
and IPv6 pinholes, source NAT and redirects, built-ins, probation for
|
||||
network changes (confirmed by the client that made them), the ruleset
|
||||
|
||||
+17
-3
@@ -35,9 +35,23 @@ func TestLimitAndDescribe(t *testing.T) {
|
||||
if _, err := parseLimit("fast"); err == nil {
|
||||
t.Error("a bad limit accepted")
|
||||
}
|
||||
m := model.Match{InZones: []string{"wan"}, NotSources: true, Sources: []string{"trusted"}, Services: []string{"ssh", "tcp/8080"}}
|
||||
if got := describe(m); got != "in wan from not trusted ssh,tcp/8080" {
|
||||
t.Errorf("describe: %q", got)
|
||||
for _, c := range []struct {
|
||||
m model.Match
|
||||
chain string
|
||||
want string
|
||||
}{
|
||||
{model.Match{InZones: []string{"wan"}, NotSources: true, Sources: []string{"trusted"}, Services: []string{"ssh", "tcp/8080"}}, "input",
|
||||
"wan → firewall from any but trusted ssh,tcp/8080"},
|
||||
{model.Match{InZones: []string{"wan"}, NotIn: true, Destinations: []string{"trusted_ips"}}, "forward", "any but wan → any to trusted_ips"},
|
||||
{model.Match{InZones: []string{"lan"}, OutZones: []string{"wan"}}, "forward", "lan → wan"},
|
||||
{model.Match{OutZones: []string{"wan"}}, "output", "firewall → wan"},
|
||||
{model.Match{OutZones: []string{"wan"}, Sources: []string{"10.0.0.0/8"}}, "", "any → wan from 10.0.0.0/8"},
|
||||
{model.Match{Sources: []string{"trusted"}}, "input", "from trusted"},
|
||||
{model.Match{}, "forward", "everything"},
|
||||
} {
|
||||
if got := describe(c.m, c.chain); got != c.want {
|
||||
t.Errorf("describe(%+v, %s) = %q, want %q", c.m, c.chain, got, c.want)
|
||||
}
|
||||
}
|
||||
if got := editList([]string{"a", "b", "c"}, []string{"d"}, []string{"b"}); len(got) != 3 || got[1] != "c" || got[2] != "d" {
|
||||
t.Errorf("editList: %v", got)
|
||||
|
||||
+34
-16
@@ -64,22 +64,40 @@ func (m *matchFlags) patch(fl *pflag.FlagSet, p patch) {
|
||||
set("family", "family", m.family)
|
||||
}
|
||||
|
||||
// describe sums up a match: "in lan from trusted ssh, dns".
|
||||
func describe(m model.Match) string {
|
||||
// describe sums up a match in a chain ("" for NAT): "lan → firewall
|
||||
// from trusted ssh,dns". Zones are a direction; the firewall itself is
|
||||
// the other end of the input and output chains.
|
||||
func describe(m model.Match, chain string) string {
|
||||
var parts []string
|
||||
side := func(word string, not bool, list []string) {
|
||||
if len(list) == 0 {
|
||||
return
|
||||
}
|
||||
list := func(not bool, xs []string) string {
|
||||
s := strings.Join(xs, ",")
|
||||
if not {
|
||||
word += " not"
|
||||
return "any but " + s
|
||||
}
|
||||
parts = append(parts, word+" "+strings.Join(list, ","))
|
||||
return s
|
||||
}
|
||||
if len(m.InZones) > 0 || len(m.OutZones) > 0 {
|
||||
in, out := "any", "any"
|
||||
if len(m.InZones) > 0 {
|
||||
in = list(m.NotIn, m.InZones)
|
||||
}
|
||||
if len(m.OutZones) > 0 {
|
||||
out = list(m.NotOut, m.OutZones)
|
||||
}
|
||||
switch chain {
|
||||
case model.ChainInput:
|
||||
out = "firewall"
|
||||
case model.ChainOutput:
|
||||
in = "firewall"
|
||||
}
|
||||
parts = append(parts, in+" → "+out)
|
||||
}
|
||||
if len(m.Sources) > 0 {
|
||||
parts = append(parts, "from "+list(m.NotSources, m.Sources))
|
||||
}
|
||||
if len(m.Destinations) > 0 {
|
||||
parts = append(parts, "to "+list(m.NotDests, m.Destinations))
|
||||
}
|
||||
side("in", m.NotIn, m.InZones)
|
||||
side("out", m.NotOut, m.OutZones)
|
||||
side("from", m.NotSources, m.Sources)
|
||||
side("to", m.NotDests, m.Destinations)
|
||||
if len(m.Services) > 0 {
|
||||
parts = append(parts, strings.Join(m.Services, ","))
|
||||
}
|
||||
@@ -281,7 +299,7 @@ func ruleCmd() *cobra.Command {
|
||||
return output(rs, func(w io.Writer) {
|
||||
fmt.Fprintln(w, "CHAIN\tID\tMATCH\tACTION\tHITS\tCOMMENT")
|
||||
for _, r := range rs {
|
||||
what := describe(r.Match)
|
||||
what := describe(r.Match, r.Chain)
|
||||
if r.Raw != "" {
|
||||
what = "raw: " + r.Raw
|
||||
}
|
||||
@@ -304,7 +322,7 @@ func ruleCmd() *cobra.Command {
|
||||
cmd.AddCommand(ls)
|
||||
cmd.AddCommand(showCmd("rules", "rule", func(w io.Writer, r model.Rule) {
|
||||
if r.Raw == "" {
|
||||
fmt.Fprintf(w, "ID:\t%d\nChain:\t%s\nMatch:\t%s%s\nAction:\t%s\n", r.ID, r.Chain, describe(r.Match), ruleExtras(r), r.Action)
|
||||
fmt.Fprintf(w, "ID:\t%d\nChain:\t%s\nMatch:\t%s%s\nAction:\t%s\n", r.ID, r.Chain, describe(r.Match, r.Chain), ruleExtras(r), r.Action)
|
||||
} else {
|
||||
fmt.Fprintf(w, "ID:\t%d\nChain:\t%s\n", r.ID, r.Chain)
|
||||
}
|
||||
@@ -700,7 +718,7 @@ func snatCmd() *cobra.Command {
|
||||
if to == "" {
|
||||
to = "masquerade"
|
||||
}
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n", x.ID, describe(x.Match), to, hits(cs, fmt.Sprintf("s%d", x.ID)), x.Comment)
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n", x.ID, describe(x.Match, ""), to, hits(cs, fmt.Sprintf("s%d", x.ID)), x.Comment)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -780,7 +798,7 @@ func redirectCmd() *cobra.Command {
|
||||
return output(xs, func(w io.Writer) {
|
||||
fmt.Fprintln(w, "ID\tMATCH\tTO PORT\tHITS\tCOMMENT")
|
||||
for _, x := range xs {
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n", x.ID, describe(x.Match), orDash(x.ToPort), hits(cs, fmt.Sprintf("d%d", x.ID)), x.Comment)
|
||||
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n", x.ID, describe(x.Match, ""), orDash(x.ToPort), hits(cs, fmt.Sprintf("d%d", x.ID)), x.Comment)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -98,11 +98,16 @@ export function matchEditor(m, n, opts = {}) {
|
||||
// describe sums up a match: "in lan · from trusted · ssh, dns".
|
||||
export function describe(m) {
|
||||
const parts = [];
|
||||
const side = (word, not, list) => { if (list && list.length) parts.push(word + (not ? " not " : " ") + list.join(", ")); };
|
||||
side("in", m.not_in, m.in_zones);
|
||||
side("out", m.not_out, m.out_zones);
|
||||
side("from", m.not_sources, m.sources);
|
||||
side("to", m.not_destinations, m.destinations);
|
||||
const list = (not, xs) => (not ? "any but " : "") + xs.join(", ");
|
||||
// Zones are a direction, "lan → wan"; the firewall itself is the other
|
||||
// end of the input and output chains.
|
||||
const ins = m.in_zones || [], outs = m.out_zones || [];
|
||||
if (ins.length || outs.length) {
|
||||
parts.push((m.chain === "output" ? "firewall" : ins.length ? list(m.not_in, ins) : "any") + " → " +
|
||||
(m.chain === "input" ? "firewall" : outs.length ? list(m.not_out, outs) : "any"));
|
||||
}
|
||||
if (m.sources && m.sources.length) parts.push("from " + list(m.not_sources, m.sources));
|
||||
if (m.destinations && m.destinations.length) parts.push("to " + list(m.not_destinations, m.destinations));
|
||||
if (m.services && m.services.length) parts.push(m.services.join(", "));
|
||||
if (m.family) parts.push(m.family + " only");
|
||||
return parts.length ? parts.join(" · ") : "everything";
|
||||
|
||||
Reference in new issue
Block a user