ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Security.IpLiteral

Description

A hand-rolled recogniser for IP literals, feeding the internal-range block.

parseIpLiteral turns a host string into an IpAddr (dotted-quad IPv4 or the IPv6 forms a host realistically carries), or Nothing for a DNS name. The recogniser is deliberately lenient on the IPv4 dotted-quad, coercing each octet exactly as inet_aton and hence a libc resolver does (leading-zero octal, 0x hex), so the policy layer tests the address that would actually be dialled rather than a decimal misreading. Range membership is delegated to iproute by the policy layer (Ecluse.Core.Security.Host); recognising the literal stays here on purpose, because delegating it to a library would change that lenient boundary. See parseIpLiteral for the exact grammar and the boundaries left unmodelled.

Synopsis

IP literals

data IpAddr Source #

An IP literal recognised from a host, for the internal-range block. The constructors are exposed so the policy layer (Ecluse.Core.Security.Host) can convert it to an iproute IP value for the range-membership test; the type carries no instances of its own.

Constructors

IpV4 Word8 Word8 Word8 Word8

An IPv4 address as its four octets.

IpV6 [Word16]

An IPv6 address, normalised to its eight 16-bit groups.

parseIpLiteral :: Text -> Maybe IpAddr Source #

Parse a host as an IP literal, or Nothing for a DNS name. Handles dotted- quad IPv4 and the IPv6 forms a host realistically carries -- full eight-group form, ::-compressed forms (including ::1), and a trailing embedded IPv4 (the a.b.c.d in ::ffff:a.b.c.d) -- which is enough to recognise the loopback, link-local, and IPv4-mapped addresses isBlockedIP blocks. It is deliberately not a complete IPv6 parser (no zone ids); an unrecognised literal is treated as a name, which the host allowlist still constrains.

Only range membership is delegated to iproute (isBlockedIP); recognising the literal stays hand-rolled on purpose. This recogniser is deliberately lenient on the IPv4 dotted-quad: it accepts the ambiguous octet spellings a strict IP library rejects and coerces each octet exactly as inet_aton -- and hence a libc resolver -- does, so the block tests the address that would actually be dialled. A 0x/0X-prefixed octet is hexadecimal, a leading-zero octet is octal, and anything else is decimal. A leading-zero octet is therefore not its decimal digits: 0012.0.0.1 is octal 10.0.0.1 (RFC1918, blocked), whereas 010.0.0.1 is octal 8.0.0.1 and 0127.0.0.1 is octal 87.0.0.1 (both public, not blocked) -- matching the resolver rather than a decimal misreading. A stricter parser that rejected these spellings would let an octal/hex spelling of an internal address skip the block and reach the resolving fetch as a name, silently narrowing the SSRF gate.

Two boundaries are deliberately not modelled here; such a host is simply treated as a name, which the host allowlist constrains. First, the short inet_aton forms with fewer than four parts (a bare 32-bit number 2130706433 / 0x7f000001, or a 127.1) are not literals here. Second, a malformed octet (an invalid-octal 08, where 8 is not an octal digit, or an overflowing 0400/256/0x100) is not a literal, exactly as a resolver rejects it. A malformed IPv6 group that overflows 16 bits (fe80::1ffff) is likewise not a literal here. Delegating literal parsing to a library would change this lenient/strict boundary, so it is kept here.

Lexical predicates (internal for testing)