| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
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.
IP literals
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.
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.