155 lines
5.7 KiB
Go
155 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestWriteJSONFileCreatesParentDirectory(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "workflow", "validation-summary.json")
|
|
if err := writeJSONFile(path, map[string]bool{"valid": true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
contents, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(contents) != "{\"valid\":true}\n" {
|
|
t.Fatalf("unexpected contents %q", contents)
|
|
}
|
|
}
|
|
|
|
func validBundle(now time.Time) bundle {
|
|
return bundle{
|
|
SchemaVersion: 1,
|
|
GeneratedAt: now.Add(-time.Minute),
|
|
ExpiresAt: now.Add(24 * time.Hour),
|
|
AdmittedIPv4: []addressRange{{Start: "192.0.2.0", End: "192.0.2.255"}, {Start: "198.51.100.1", End: "198.51.100.1"}},
|
|
ExceptionIPv4: []addressRange{{Start: "203.0.113.0", End: "203.0.113.15"}},
|
|
AdmittedIntervalCount: 2,
|
|
AdmittedAddressCount: 257,
|
|
ExceptionIntervalCount: 1,
|
|
ExceptionAddressCount: 16,
|
|
}
|
|
}
|
|
|
|
func TestValidateAndRender(t *testing.T) {
|
|
now := time.Date(2026, 7, 12, 0, 0, 0, 0, time.UTC)
|
|
validated, err := validateBundle(now, validBundle(now))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var output bytes.Buffer
|
|
if err := render(&output, validated, "ens3"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, expected := range []string{
|
|
"delete table inet oci_l4_relay_web_allow",
|
|
"192.0.2.0-192.0.2.255",
|
|
"198.51.100.1",
|
|
"203.0.113.0-203.0.113.15",
|
|
"iifname \"ens3\" tcp dport { 80, 443, 465, 993 }",
|
|
"counter name web_allow_denied drop",
|
|
} {
|
|
if !strings.Contains(output.String(), expected) {
|
|
t.Errorf("rendered output does not contain %q", expected)
|
|
}
|
|
}
|
|
if strings.Contains(output.String(), "587") {
|
|
t.Fatal("rendered output admits cluster-local submission")
|
|
}
|
|
}
|
|
|
|
func TestValidationFailures(t *testing.T) {
|
|
now := time.Date(2026, 7, 12, 0, 0, 0, 0, time.UTC)
|
|
tests := map[string]func(*bundle){
|
|
"schema": func(value *bundle) { value.SchemaVersion = 2 },
|
|
"future": func(value *bundle) { value.GeneratedAt = now.Add(10 * time.Minute) },
|
|
"expiry": func(value *bundle) { value.ExpiresAt = now.Add(30 * time.Minute) },
|
|
"lifetime": func(value *bundle) { value.ExpiresAt = now.Add(8 * 24 * time.Hour) },
|
|
"overlap": func(value *bundle) { value.AdmittedIPv4[1] = addressRange{Start: "192.0.2.128", End: "192.0.2.200"} },
|
|
"reversed": func(value *bundle) { value.AdmittedIPv4[0] = addressRange{Start: "192.0.2.10", End: "192.0.2.1"} },
|
|
"ipv6": func(value *bundle) { value.AdmittedIPv4[0] = addressRange{Start: "2001:db8::1", End: "2001:db8::2"} },
|
|
"noncanonical": func(value *bundle) { value.AdmittedIPv4[0].Start = "192.000.002.000" },
|
|
"address count": func(value *bundle) { value.AdmittedAddressCount++ },
|
|
}
|
|
for name, mutate := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
value := validBundle(now)
|
|
mutate(&value)
|
|
if _, err := validateBundle(now, value); err == nil {
|
|
t.Fatal("expected validation failure")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRejectsUnsafeInterface(t *testing.T) {
|
|
now := time.Date(2026, 7, 12, 0, 0, 0, 0, time.UTC)
|
|
validated, err := validateBundle(now, validBundle(now))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := render(&bytes.Buffer{}, validated, "ens3\nflush ruleset"); err == nil {
|
|
t.Fatal("expected unsafe interface rejection")
|
|
}
|
|
}
|
|
|
|
func TestRenderFailClosed(t *testing.T) {
|
|
var output bytes.Buffer
|
|
if err := renderFailClosed(&output, "ens3"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(output.String(), "iifname \"ens3\" tcp dport { 80, 443, 465, 993 } counter name web_allow_denied drop") {
|
|
t.Fatal("fail-closed ruleset does not drop every positive-policy surface")
|
|
}
|
|
}
|
|
|
|
func TestPolicyBounds(t *testing.T) {
|
|
now := time.Date(2026, 7, 12, 0, 0, 0, 0, time.UTC)
|
|
validated, err := validateBundle(now, validBundle(now))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := validatePolicy(updateConfig{minimumAddresses: 257, maximumAddresses: 257, minimumIntervals: 2, maximumExceptionAddresses: 16, maximumExceptionIntervals: 1}, validated); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, configuration := range map[string]updateConfig{
|
|
"minimum addresses": {minimumAddresses: 258, maximumAddresses: 1 << 32, minimumIntervals: 1, maximumExceptionAddresses: 1 << 32, maximumExceptionIntervals: maxExceptions},
|
|
"maximum addresses": {minimumAddresses: 1, maximumAddresses: 256, minimumIntervals: 1, maximumExceptionAddresses: 1 << 32, maximumExceptionIntervals: maxExceptions},
|
|
"minimum intervals": {minimumAddresses: 1, maximumAddresses: 1 << 32, minimumIntervals: 3, maximumExceptionAddresses: 1 << 32, maximumExceptionIntervals: maxExceptions},
|
|
"maximum exception addresses": {minimumAddresses: 1, maximumAddresses: 1 << 32, minimumIntervals: 1, maximumExceptionAddresses: 15, maximumExceptionIntervals: maxExceptions},
|
|
"maximum exception intervals": {minimumAddresses: 1, maximumAddresses: 1 << 32, minimumIntervals: 1, maximumExceptionAddresses: 1 << 32, maximumExceptionIntervals: 0},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
if err := validatePolicy(configuration, validated); err == nil {
|
|
t.Fatal("expected policy-bound failure")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddStaticExceptions(t *testing.T) {
|
|
now := time.Date(2026, 7, 12, 0, 0, 0, 0, time.UTC)
|
|
validated, err := validateBundle(now, validBundle(now))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
validated, err = addStaticExceptions(validated, []string{"203.0.113.16", "198.51.100.50"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var output bytes.Buffer
|
|
if err := render(&output, validated, "ens3"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, expected := range []string{"198.51.100.50", "203.0.113.0-203.0.113.16"} {
|
|
if !strings.Contains(output.String(), expected) {
|
|
t.Errorf("rendered output does not contain %q", expected)
|
|
}
|
|
}
|
|
}
|