Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for variables in host/port lists #41

Merged
merged 3 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ task test: %i[spec features]

task default: :test

task feature: :gen_parser
task build: :gen_parser
task spec: :gen_parser

desc 'Generate the puffy language parser'
task gen_parser: 'lib/puffy/parser.tab.rb'
Expand Down
19 changes: 13 additions & 6 deletions lib/puffy/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rule
variable_value: ADDRESS { result = val[0][:value] }
| STRING { result = val[0][:value] }
| VARIABLE { result = @variables.fetch(val[0][:value]) }
| port { result = val[0] }

service: SERVICE service_name block { @services[val[1]] = val[2] }

Expand Down Expand Up @@ -132,9 +133,12 @@ rule
| PORT port { result = val[1] }
|

port_list: port_list ',' port { result = val[0] + [val[2]] }
| port_list port { result = val[0] + [val[1]] }
| port { result = [val[0]] }
port_list: port_list ',' port_list_item { result = val[0] + val[2] }
| port_list port_list_item { result = val[0] + val[1] }
| port_list_item { result = val[0] }

port_list_item: port { result = [val[0]] }
| VARIABLE { result = @variables.fetch(val[0][:value]) }

port: INTEGER { result = val[0][:value] }
| IDENTIFIER { result = val[0][:value] }
Expand All @@ -143,9 +147,12 @@ rule
host: ADDRESS { result = val[0][:value] }
| STRING { result = val[0][:value] }

host_list: host_list ',' host { result = val[0] + [val[2]] }
| host_list host { result = val[0] + [val[1]] }
| host { result = [val[0]] }
host_list: host_list ',' host_list_item { result = val[0] + val[2] }
| host_list host_list_item { result = val[0] + val[1] }
| host_list_item { result = val[0] }

host_list_item: host { result = [val[0]] }
| VARIABLE { result = @variables.fetch(val[0][:value]) }

filteropts: filteropts ',' filteropt { result = val[0].merge(val[2]) }
| filteropts filteropt { result = val[0].merge(val[1]) }
Expand Down
27 changes: 19 additions & 8 deletions spec/puffy/resolver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,32 @@
# https://github.com/jordansissel/experiments/tree/master/ruby/dns-resolving-bug
module Puffy
RSpec.describe Resolver do
subject { Puffy::Resolver.instance }
before(:each) do
dns = Resolv::DNS.new
allow(dns).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('203.0.113.42')])
allow(dns).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([Resolv::DNS::Resource::IN::AAAA.new('2001:db8:fa4e:adde::42')])
allow(dns).to receive(:getresources).with('host.invalid.', Resolv::DNS::Resource::IN::A).and_call_original
allow(dns).to receive(:getresources).with('host.invalid.', Resolv::DNS::Resource::IN::AAAA).and_call_original

allow(Resolv::DNS).to receive(:open).with(nil).and_return(dns)
end

subject { Puffy::Resolver.clone.instance }

it 'resolves IPv4 and IPv6' do
expect(subject.resolv('example.com').collect(&:to_s)).to eq(['2606:2800:220:1:248:1893:25c8:1946', '93.184.216.34'])
expect(subject.resolv('example.com').collect(&:to_s)).to eq(['2001:db8:fa4e:adde::42', '203.0.113.42'])
end

it 'resolves IPv4 only' do
expect(subject.resolv('example.com', :inet).collect(&:to_s)).to eq(['93.184.216.34'])
expect(subject.resolv(IPAddr.new('93.184.216.34'), :inet).collect(&:to_s)).to eq(['93.184.216.34'])
expect(subject.resolv(IPAddr.new('2606:2800:220:1:248:1893:25c8:1946'), :inet).collect(&:to_s)).to eq([])
expect(subject.resolv('example.com', :inet).collect(&:to_s)).to eq(['203.0.113.42'])
expect(subject.resolv(IPAddr.new('203.0.113.27'), :inet).collect(&:to_s)).to eq(['203.0.113.27'])
expect(subject.resolv(IPAddr.new('2001:db8:c0ff:ee::42'), :inet).collect(&:to_s)).to eq([])
end

it 'resolves IPv6 only' do
expect(subject.resolv('example.com', :inet6).collect(&:to_s)).to eq(['2606:2800:220:1:248:1893:25c8:1946'])
expect(subject.resolv(IPAddr.new('93.184.216.34'), :inet6).collect(&:to_s)).to eq([])
expect(subject.resolv(IPAddr.new('2606:2800:220:1:248:1893:25c8:1946'), :inet6).collect(&:to_s)).to eq(['2606:2800:220:1:248:1893:25c8:1946'])
expect(subject.resolv('example.com', :inet6).collect(&:to_s)).to eq(['2001:db8:fa4e:adde::42'])
expect(subject.resolv(IPAddr.new('203.0.113.27'), :inet6).collect(&:to_s)).to eq([])
expect(subject.resolv(IPAddr.new('2001:db8:c0ff:ee::42'), :inet6).collect(&:to_s)).to eq(['2001:db8:c0ff:ee::42'])
end

it 'raises exceptions with unknown hosts' do
Expand Down
28 changes: 15 additions & 13 deletions spec/puffy/rule_factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ module Puffy
end

it 'passes addresses and networks' do
result = subject.build(to: [{ host: IPAddr.new('192.0.2.1') }])
result = subject.build(to: [{ host: IPAddr.new('203.0.113.42') }])

expect(result.count).to eq(1)
expect(result[0].to[:host]).to eq(IPAddr.new('192.0.2.1'))
expect(result[0].to[:host]).to eq(IPAddr.new('203.0.113.42'))

result = subject.build(to: [{ host: IPAddr.new('192.0.2.0/24') }])

Expand All @@ -39,13 +39,13 @@ module Puffy

it 'resolves hostnames' do
expect(Rule).to receive(:new).twice.and_call_original
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:DB8::1'), IPAddr.new('192.0.2.1')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:db8:fa4e:adde::42'), IPAddr.new('203.0.113.42')])

result = subject.build(to: [{ host: 'example.com' }])

expect(result.count).to eq(2)
expect(result[0].to[:host]).to eq(IPAddr.new('2001:DB8::1'))
expect(result[1].to[:host]).to eq(IPAddr.new('192.0.2.1'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::42'))
expect(result[1].to[:host]).to eq(IPAddr.new('203.0.113.42'))
end

it 'accepts service names' do
Expand Down Expand Up @@ -77,18 +77,18 @@ module Puffy
end

it 'does not mix IPv4 and IPv6' do
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.net').and_return([IPAddr.new('2001:DB8::FFFF:FFFF:FFFF'), IPAddr.new('198.51.100.1')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:DB8::1'), IPAddr.new('192.0.2.1')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.net').and_return([IPAddr.new('2001:db8:fa4e:adde::27'), IPAddr.new('203.0.113.27')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:db8:fa4e:adde::42'), IPAddr.new('203.0.113.42')])

expect(Rule).to receive(:new).exactly(4).times.and_call_original

result = subject.build(from: [{ host: 'example.net' }], to: [{ host: 'example.com' }])

expect(result.count).to eq(2)
expect(result[0].from[:host]).to eq(IPAddr.new('2001:DB8::FFFF:FFFF:FFFF'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:DB8::1'))
expect(result[1].from[:host]).to eq(IPAddr.new('198.51.100.1'))
expect(result[1].to[:host]).to eq(IPAddr.new('192.0.2.1'))
expect(result[0].from[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::27'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::42'))
expect(result[1].from[:host]).to eq(IPAddr.new('203.0.113.27'))
expect(result[1].to[:host]).to eq(IPAddr.new('203.0.113.42'))
end

it 'filters address family' do
Expand All @@ -100,19 +100,21 @@ module Puffy
end

it 'limits scope to IP version' do
expect(Puffy::Resolver.instance).to receive(:resolv).twice.with('example.com').and_return([IPAddr.new('2001:db8:fa4e:adde::42'), IPAddr.new('203.0.113.42')])

result = []

subject.ipv4 do
result = subject.build(to: [{ host: 'example.com' }])
end
expect(result.count).to eq(1)
expect(result[0].to[:host]).to eq(IPAddr.new('93.184.216.34'))
expect(result[0].to[:host]).to eq(IPAddr.new('203.0.113.42'))

subject.ipv6 do
result = subject.build(to: [{ host: 'example.com' }])
end
expect(result.count).to eq(1)
expect(result[0].to[:host]).to eq(IPAddr.new('2606:2800:220:1:248:1893:25c8:1946'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::42'))
end
end
end
14 changes: 7 additions & 7 deletions spec/puffy/rule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ module Puffy
it 'detects IPv4 rules' do
expect(Rule.new.ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { port: 80 }).ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('192.0.2.1'), port: 80 }).ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:DB8::1'), port: 80 }).ipv4?).to be_falsy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('203.0.113.42'), port: 80 }).ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:db8:fa4e:adde::42'), port: 80 }).ipv4?).to be_falsy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').ipv4?).to be_truthy
end

it 'detects IPv6 rules' do
expect(Rule.new.ipv6?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { port: 80 }).ipv6?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('192.0.2.1'), port: 80 }).ipv6?).to be_falsy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:DB8::1'), port: 80 }).ipv6?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('203.0.113.42'), port: 80 }).ipv6?).to be_falsy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:db8:fa4e:adde::42'), port: 80 }).ipv6?).to be_truthy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').ipv6?).to be_truthy
end

Expand All @@ -45,23 +45,23 @@ module Puffy
expect(Rule.new.rdr?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, proto: :tcp, to: { port: 80 }).rdr?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, on: 'eth0', nat_to: IPAddr.new('198.51.100.72')).rdr?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('192.0.2.1') }).rdr?).to be_truthy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('203.0.113.42') }).rdr?).to be_truthy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').rdr?).to be_falsy
end

it 'detects NAT rules' do
expect(Rule.new.nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, proto: :tcp, to: { port: 80 }).nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, on: 'eth0', nat_to: IPAddr.new('198.51.100.72')).nat?).to be_truthy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('192.0.2.1') }).nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('203.0.113.42') }).nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').nat?).to be_falsy
end

it 'detects forward rules' do
expect(Rule.new.fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, proto: :tcp, to: { port: 80 }).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, on: 'eth0', nat_to: IPAddr.new('198.51.100.72')).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('192.0.2.1') }).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('203.0.113.42') }).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').fwd?).to be_truthy
end

Expand Down
Loading