1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2026-06-13 09:45:43 +00:00
الملفات
postal/spec/lib/postal/http/address_guard_spec.rb
Adam Cooke 029bfe098d fix(specs): stub IPv6 support in address guard IPv6 literal spec
The spec relied on the test machine having real IPv6 connectivity,
which GitHub Actions runners do not have.
2026-06-03 15:43:02 +01:00

193 أسطر
5.5 KiB
Ruby

# frozen_string_literal: true
require "rails_helper"
RSpec.describe Postal::HTTP::AddressGuard do
describe ".safe_connect_address" do
subject(:call) { described_class.safe_connect_address(host) }
before do
allow(Postal::Config.postal).to receive(:allowed_request_destinations).and_return(allowlist)
end
let(:allowlist) { [] }
context "when given a public IP literal" do
let(:host) { "93.184.216.34" }
it "returns the address to connect to" do
expect(call).to eq "93.184.216.34"
end
end
context "when given a public IPv6 literal" do
let(:host) { "2606:2800:220:1:248:1893:25c8:1946" }
before { allow(described_class).to receive(:ipv6_supported?).and_return(true) }
it "returns the address to connect to" do
expect(call).to eq "2606:2800:220:1:248:1893:25c8:1946"
end
end
[
"127.0.0.1",
"10.0.0.1",
"172.16.5.4",
"192.168.1.1",
"169.254.169.254", # cloud metadata
"100.64.0.1", # carrier-grade NAT
"0.0.0.0",
"::1",
"fd00::1", # unique-local IPv6
"fe80::1", # link-local IPv6
"::ffff:127.0.0.1", # IPv4-mapped loopback
].each do |blocked|
context "when given the blocked address #{blocked}" do
let(:host) { blocked }
it "raises BlockedDestinationError" do
expect { call }.to raise_error(Postal::HTTP::BlockedDestinationError)
end
end
end
context "when given a hostname that resolves to a public address" do
let(:host) { "example.com" }
before do
allow(Resolv).to receive(:getaddresses).with(host).and_return(["93.184.216.34"])
end
it "returns the resolved address" do
expect(call).to eq "93.184.216.34"
end
end
context "when given a hostname that resolves to a private address" do
let(:host) { "internal.example.com" }
before do
allow(Resolv).to receive(:getaddresses).with(host).and_return(["10.1.2.3"])
end
it "raises BlockedDestinationError" do
expect { call }.to raise_error(Postal::HTTP::BlockedDestinationError)
end
end
context "when a hostname resolves to both a public and a private address" do
let(:host) { "rebind.example.com" }
before do
allow(Resolv).to receive(:getaddresses).with(host).and_return(["93.184.216.34", "127.0.0.1"])
end
it "raises BlockedDestinationError because one address is blocked" do
expect { call }.to raise_error(Postal::HTTP::BlockedDestinationError)
end
end
context "when a hostname resolves to both IPv4 and IPv6 addresses" do
let(:host) { "dualstack.example.com" }
let(:ipv6) { "2606:2800:220:1:248:1893:25c8:1946" }
before do
allow(Resolv).to receive(:getaddresses).with(host).and_return([ipv6, "93.184.216.34"])
end
context "and the server does not support IPv6" do
before { allow(described_class).to receive(:ipv6_supported?).and_return(false) }
it "connects over IPv4" do
expect(call).to eq "93.184.216.34"
end
end
context "and the server supports IPv6" do
before { allow(described_class).to receive(:ipv6_supported?).and_return(true) }
it "still prefers IPv4 for predictability" do
expect(call).to eq "93.184.216.34"
end
end
end
context "when a hostname resolves only to an IPv6 address" do
let(:host) { "v6only.example.com" }
let(:ipv6) { "2606:2800:220:1:248:1893:25c8:1946" }
before do
allow(Resolv).to receive(:getaddresses).with(host).and_return([ipv6])
end
context "and the server does not support IPv6" do
before { allow(described_class).to receive(:ipv6_supported?).and_return(false) }
it "raises a SocketError because the address is unreachable" do
expect { call }.to raise_error(SocketError)
end
end
context "and the server supports IPv6" do
before { allow(described_class).to receive(:ipv6_supported?).and_return(true) }
it "connects over IPv6" do
expect(call).to eq ipv6
end
end
end
context "when a hostname cannot be resolved" do
let(:host) { "nope.example.com" }
before do
allow(Resolv).to receive(:getaddresses).with(host).and_return([])
end
it "raises BlockedDestinationError" do
expect { call }.to raise_error(Postal::HTTP::BlockedDestinationError, /resolve/)
end
end
context "when the host is blank" do
let(:host) { "" }
it "raises BlockedDestinationError" do
expect { call }.to raise_error(Postal::HTTP::BlockedDestinationError)
end
end
context "when a blocked address is allowlisted by CIDR" do
let(:host) { "10.0.0.5" }
let(:allowlist) { ["10.0.0.0/8"] }
it "returns the address" do
expect(call).to eq "10.0.0.5"
end
end
context "when a blocked address is allowlisted by exact IP" do
let(:host) { "127.0.0.1" }
let(:allowlist) { ["127.0.0.1"] }
it "returns the address" do
expect(call).to eq "127.0.0.1"
end
end
context "when a hostname resolving to a private address is allowlisted by name" do
let(:host) { "internal.example.com" }
let(:allowlist) { ["internal.example.com"] }
before do
allow(Resolv).to receive(:getaddresses).with(host).and_return(["10.1.2.3"])
end
it "returns the resolved address" do
expect(call).to eq "10.1.2.3"
end
end
end
end