Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

155 lines
5.8 KiB
Go

package cli
import (
"errors"
"net"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
// withInterfaceAddrs swaps interfaceAddrsFn for the duration of one spec.
// Ginkgo's DeferCleanup restores the original after the spec finishes, so
// concurrent specs can each pretend to be running on a different host.
func withInterfaceAddrs(cidrs ...string) {
original := interfaceAddrsFn
interfaceAddrsFn = func() ([]net.Addr, error) {
out := make([]net.Addr, 0, len(cidrs))
for _, c := range cidrs {
ip, ipnet, err := net.ParseCIDR(c)
Expect(err).ToNot(HaveOccurred())
out = append(out, &net.IPNet{IP: ip, Mask: ipnet.Mask})
}
return out, nil
}
DeferCleanup(func() { interfaceAddrsFn = original })
}
func withInterfaceAddrsErr(err error) {
original := interfaceAddrsFn
interfaceAddrsFn = func() ([]net.Addr, error) { return nil, err }
DeferCleanup(func() { interfaceAddrsFn = original })
}
var _ = Describe("requireAuthOrTrustedBind", func() {
BeforeEach(func() {
// Default to "host has only loopback" — the literal-IP cases below
// don't touch interfaceAddrsFn but the wildcard cases do, and a
// loopback-only host is the safest default for those.
withInterfaceAddrs("127.0.0.1/8", "::1/128")
})
It("permits any bind when auth is configured", func() {
Expect(requireAuthOrTrustedBind("0.0.0.0:8080", true, false)).To(Succeed())
Expect(requireAuthOrTrustedBind("203.0.113.5:8080", true, false)).To(Succeed())
})
It("permits any bind when --allow-insecure-public-bind is set", func() {
Expect(requireAuthOrTrustedBind("0.0.0.0:8080", false, true)).To(Succeed())
Expect(requireAuthOrTrustedBind("203.0.113.5:8080", false, true)).To(Succeed())
})
Context("literal IP binds", func() {
It("permits loopback", func() {
Expect(requireAuthOrTrustedBind("127.0.0.1:8080", false, false)).To(Succeed())
Expect(requireAuthOrTrustedBind("[::1]:8080", false, false)).To(Succeed())
Expect(requireAuthOrTrustedBind("127.5.4.3:8080", false, false)).To(Succeed())
})
DescribeTable("permits private LAN ranges",
func(addr string) {
Expect(requireAuthOrTrustedBind(addr, false, false)).To(Succeed())
},
Entry("RFC 1918 — 10/8", "10.0.0.5:8080"),
Entry("RFC 1918 — 172.16/12", "172.16.5.5:8080"),
Entry("RFC 1918 — 192.168/16", "192.168.1.5:8080"),
Entry("IPv6 ULA — fc00::/7", "[fc00::1]:8080"),
Entry("IPv6 ULA — fd00::/8", "[fd12:3456:789a::1]:8080"),
)
It("permits link-local addresses", func() {
Expect(requireAuthOrTrustedBind("169.254.10.10:8080", false, false)).To(Succeed())
Expect(requireAuthOrTrustedBind("[fe80::1]:8080", false, false)).To(Succeed())
})
It("permits CGNAT (Tailscale default)", func() {
Expect(requireAuthOrTrustedBind("100.64.0.5:8080", false, false)).To(Succeed())
Expect(requireAuthOrTrustedBind("100.127.255.1:8080", false, false)).To(Succeed())
})
It("rejects boundary addresses just outside CGNAT", func() {
Expect(requireAuthOrTrustedBind("100.63.255.255:8080", false, false)).To(HaveOccurred())
Expect(requireAuthOrTrustedBind("100.128.0.0:8080", false, false)).To(HaveOccurred())
})
It("rejects public IPv4", func() {
Expect(requireAuthOrTrustedBind("8.8.8.8:8080", false, false)).To(HaveOccurred())
Expect(requireAuthOrTrustedBind("203.0.113.5:8080", false, false)).To(HaveOccurred())
})
It("rejects public IPv6", func() {
Expect(requireAuthOrTrustedBind("[2001:db8::1]:8080", false, false)).To(HaveOccurred())
})
})
Context("wildcard bind (`:port`, 0.0.0.0, ::)", func() {
It("permits when every interface is private/loopback", func() {
withInterfaceAddrs("127.0.0.1/8", "::1/128", "10.0.0.5/24", "fc00::1/64")
Expect(requireAuthOrTrustedBind(":8080", false, false)).To(Succeed())
Expect(requireAuthOrTrustedBind("0.0.0.0:8080", false, false)).To(Succeed())
Expect(requireAuthOrTrustedBind("[::]:8080", false, false)).To(Succeed())
})
It("permits when interfaces are loopback + Tailscale CGNAT", func() {
withInterfaceAddrs("127.0.0.1/8", "::1/128", "100.65.10.20/32")
Expect(requireAuthOrTrustedBind("0.0.0.0:8080", false, false)).To(Succeed())
})
It("rejects when ANY interface has a public IP", func() {
withInterfaceAddrs("127.0.0.1/8", "::1/128", "10.0.0.5/24", "203.0.113.42/24")
Expect(requireAuthOrTrustedBind(":8080", false, false)).To(HaveOccurred())
Expect(requireAuthOrTrustedBind("0.0.0.0:8080", false, false)).To(HaveOccurred())
Expect(requireAuthOrTrustedBind("[::]:8080", false, false)).To(HaveOccurred())
})
It("fails closed when interface enumeration errors", func() {
withInterfaceAddrsErr(errors.New("enumeration disabled"))
Expect(requireAuthOrTrustedBind("0.0.0.0:8080", false, false)).To(HaveOccurred())
})
It("fails closed when the host has no addresses at all", func() {
withInterfaceAddrs()
Expect(requireAuthOrTrustedBind("0.0.0.0:8080", false, false)).To(HaveOccurred())
})
})
Context("hostname binds", func() {
It("permits 'localhost' (resolves to loopback)", func() {
Expect(requireAuthOrTrustedBind("localhost:8080", false, false)).To(Succeed())
})
})
Context("malformed input", func() {
It("rejects an address with no port", func() {
Expect(requireAuthOrTrustedBind("8080", false, false)).To(HaveOccurred())
})
It("rejects an empty address", func() {
Expect(requireAuthOrTrustedBind("", false, false)).To(HaveOccurred())
})
})
Context("error message", func() {
It("guides the operator with all four escape hatches", func() {
err := requireAuthOrTrustedBind("203.0.113.5:8080", false, false)
Expect(err).To(HaveOccurred())
msg := err.Error()
Expect(msg).To(ContainSubstring("--auth"))
Expect(msg).To(ContainSubstring("--api-keys"))
Expect(msg).To(ContainSubstring("--allow-insecure-public-bind"))
Expect(msg).To(ContainSubstring("LAN"))
Expect(msg).To(ContainSubstring("VPN"))
})
})
})