Skip to content

Commit 4a0cb70

Browse files
committed
Add a new firewall ingress-policy "isolated"
IngressPolicyIsolated ("isolated") behaves similar to ingress policy "same-bridge" with the exception that connections from the same bridge are also blocked. This is meant to be functionally equivalent to Docker network option "enable_icc" when set to false. Signed-off-by: Swagat Bora <[email protected]>
1 parent abfac4a commit 4a0cb70

File tree

3 files changed

+176
-104
lines changed

3 files changed

+176
-104
lines changed

plugins/meta/firewall/firewall.go

+7
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ const (
6464
// IngressPolicySameBridge executes `iptables` regardless to the value of `Backend`.
6565
// IngressPolicySameBridge may not work as expected for non-bridge networks.
6666
IngressPolicySameBridge IngressPolicy = "same-bridge"
67+
68+
// IngressPolicyIsolated ("isolated"): similar to ingress policy "same-bridge" with the exception
69+
// that connections from the same bridge are also blocked.
70+
// This is equivalent to Docker network option "enable_icc" when set to false.
71+
// IngressPolicyIsolated executes `iptables` regardless to the value of `Backend`.
72+
// IngressPolicyIsolated may not work as expected for non-bridge networks.
73+
IngressPolicyIsolated IngressPolicy = "isolated"
6774
)
6875

6976
type FirewallBackend interface {

plugins/meta/firewall/firewall_integ_test.go

+131-96
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"os"
2121
"os/exec"
2222
"path/filepath"
23-
"strings"
2423

2524
. "github.com/onsi/ginkgo/v2"
2625
. "github.com/onsi/gomega"
@@ -31,6 +30,8 @@ import (
3130
"github.com/containernetworking/plugins/pkg/testutils"
3231
)
3332

33+
const nsCount = 3
34+
3435
// The integration tests expect the "firewall" binary to be present in $PATH.
3536
// To run test, e.g, : go test -exec "sudo -E PATH=$(pwd):/opt/cni/bin:$PATH" -v -ginkgo.v
3637
var _ = Describe("firewall integration tests (ingressPolicy: same-bridge)", func() {
@@ -39,24 +40,23 @@ var _ = Describe("firewall integration tests (ingressPolicy: same-bridge)", func
3940
// ns2: bar (10.88.4.0/24)
4041
//
4142
// ns0@foo can talk to ns1@foo, but cannot talk to ns2@bar
42-
const nsCount = 3
43+
4344
var (
4445
configListFoo *libcni.NetworkConfigList // "foo", 10.88.3.0/24
4546
configListBar *libcni.NetworkConfigList // "bar", 10.88.4.0/24
4647
cniConf *libcni.CNIConfig
4748
namespaces [nsCount]ns.NetNS
49+
results [nsCount]*types100.Result
4850
)
4951

50-
BeforeEach(func() {
51-
var err error
52-
rawConfigFoo := `
53-
{
52+
createNetworkConfig := func(name string, subnet string, gateway string, ingressPolicy string) string {
53+
return fmt.Sprintf(`{
5454
"cniVersion": "1.0.0",
55-
"name": "foo",
55+
"name": "%s",
5656
"plugins": [
5757
{
5858
"type": "bridge",
59-
"bridge": "foo",
59+
"bridge": "%s",
6060
"isGateway": true,
6161
"ipMasq": true,
6262
"hairpinMode": true,
@@ -70,8 +70,8 @@ var _ = Describe("firewall integration tests (ingressPolicy: same-bridge)", func
7070
"ranges": [
7171
[
7272
{
73-
"subnet": "10.88.3.0/24",
74-
"gateway": "10.88.3.1"
73+
"subnet": "%s",
74+
"gateway": "%s"
7575
}
7676
]
7777
]
@@ -80,19 +80,14 @@ var _ = Describe("firewall integration tests (ingressPolicy: same-bridge)", func
8080
{
8181
"type": "firewall",
8282
"backend": "iptables",
83-
"ingressPolicy": "same-bridge"
83+
"ingressPolicy": "%s"
8484
}
8585
]
86-
}
87-
`
88-
configListFoo, err = libcni.ConfListFromBytes([]byte(rawConfigFoo))
89-
Expect(err).NotTo(HaveOccurred())
90-
91-
rawConfigBar := strings.ReplaceAll(rawConfigFoo, "foo", "bar")
92-
rawConfigBar = strings.ReplaceAll(rawConfigBar, "10.88.3.", "10.88.4.")
86+
}`, name, name, subnet, gateway, ingressPolicy)
87+
}
9388

94-
configListBar, err = libcni.ConfListFromBytes([]byte(rawConfigBar))
95-
Expect(err).NotTo(HaveOccurred())
89+
BeforeEach(func() {
90+
var err error
9691

9792
// turn PATH in to CNI_PATH.
9893
_, err = exec.LookPath("firewall")
@@ -116,89 +111,129 @@ var _ = Describe("firewall integration tests (ingressPolicy: same-bridge)", func
116111
}
117112
})
118113

119-
Describe("Testing with network foo and bar", func() {
120-
It("should isolate foo from bar", func() {
121-
var results [nsCount]*types100.Result
122-
for i := 0; i < nsCount; i++ {
123-
runtimeConfig := libcni.RuntimeConf{
124-
ContainerID: fmt.Sprintf("test-cni-firewall-%d", i),
125-
NetNS: namespaces[i].Path(),
126-
IfName: "eth0",
127-
}
128-
129-
configList := configListFoo
130-
switch i {
131-
case 0, 1:
132-
// leave foo
133-
default:
134-
configList = configListBar
135-
}
136-
137-
// Clean up garbages produced during past failed executions
138-
_ = cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
139-
140-
// Make delete idempotent, so we can clean up on failure
141-
netDeleted := false
142-
deleteNetwork := func() error {
143-
if netDeleted {
144-
return nil
145-
}
146-
netDeleted = true
147-
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
148-
}
149-
// Create the network
150-
res, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
151-
Expect(err).NotTo(HaveOccurred())
152-
// nolint: errcheck
153-
defer deleteNetwork()
154-
155-
results[i], err = types100.NewResultFromResult(res)
156-
Expect(err).NotTo(HaveOccurred())
157-
fmt.Fprintf(GinkgoWriter, "results[%d]: %+v\n", i, results[i])
158-
}
159-
ping := func(src, dst int) error {
160-
return namespaces[src].Do(func(ns.NetNS) error {
161-
defer GinkgoRecover()
162-
saddr := results[src].IPs[0].Address.IP.String()
163-
daddr := results[dst].IPs[0].Address.IP.String()
164-
srcNetName := results[src].Interfaces[0].Name
165-
dstNetName := results[dst].Interfaces[0].Name
166-
167-
fmt.Fprintf(GinkgoWriter, "ping %s (ns%d@%s) -> %s (ns%d@%s)...",
168-
saddr, src, srcNetName, daddr, dst, dstNetName)
169-
timeoutSec := 1
170-
if err := testutils.Ping(saddr, daddr, timeoutSec); err != nil {
171-
fmt.Fprintln(GinkgoWriter, "unpingable")
172-
return err
173-
}
174-
fmt.Fprintln(GinkgoWriter, "pingable")
175-
return nil
176-
})
177-
}
178-
179-
// ns0@foo can ping to ns1@foo
180-
err := ping(0, 1)
114+
Describe("Testing with ingress-policy 'same-bridge", func() {
115+
BeforeEach(func() {
116+
var err error
117+
configListFoo, err = libcni.ConfListFromBytes([]byte(
118+
createNetworkConfig("foo", "10.88.3.0/24", "10.88.3.1", "same-bridge")))
181119
Expect(err).NotTo(HaveOccurred())
182120

183-
// ns1@foo can ping to ns0@foo
184-
err = ping(1, 0)
121+
configListBar, err = libcni.ConfListFromBytes([]byte(
122+
createNetworkConfig("bar", "10.88.4.0/24", "10.88.4.1", "same-bridge")))
185123
Expect(err).NotTo(HaveOccurred())
186124

187-
// ns0@foo cannot ping to ns2@bar
188-
err = ping(0, 2)
189-
Expect(err).To(HaveOccurred())
125+
results = setupNetworks(cniConf, namespaces, configListFoo, configListBar)
126+
})
127+
128+
Context("when testing connectivity", func() {
129+
It("should allow communication within foo network", func() {
130+
err := ping(namespaces, results, 0, 1)
131+
Expect(err).To(Succeed())
132+
err = ping(namespaces, results, 1, 0)
133+
Expect(err).To(Succeed())
134+
})
135+
136+
It("should prevent communication between foo and bar networks", func() {
137+
err := ping(namespaces, results, 0, 2)
138+
Expect(err).To(HaveOccurred())
139+
err = ping(namespaces, results, 1, 2)
140+
Expect(err).To(HaveOccurred())
141+
err = ping(namespaces, results, 2, 0)
142+
Expect(err).To(HaveOccurred())
143+
err = ping(namespaces, results, 2, 1)
144+
Expect(err).To(HaveOccurred())
145+
})
146+
})
147+
})
190148

191-
// ns1@foo cannot ping to ns2@bar
192-
err = ping(1, 2)
193-
Expect(err).To(HaveOccurred())
149+
Describe("Testing with ingress-policy 'isolated", func() {
150+
BeforeEach(func() {
151+
var err error
152+
configListFoo, err = libcni.ConfListFromBytes([]byte(
153+
createNetworkConfig("foo", "10.88.3.0/24", "10.88.3.1", "isolated")))
154+
Expect(err).NotTo(HaveOccurred())
194155

195-
// ns2@bar cannot ping to ns0@foo
196-
err = ping(2, 0)
197-
Expect(err).To(HaveOccurred())
156+
configListBar, err = libcni.ConfListFromBytes([]byte(
157+
createNetworkConfig("bar", "10.88.4.0/24", "10.88.4.1", "isolated")))
158+
Expect(err).NotTo(HaveOccurred())
198159

199-
// ns2@bar cannot ping to ns1@foo
200-
err = ping(2, 1)
201-
Expect(err).To(HaveOccurred())
160+
results = setupNetworks(cniConf, namespaces, configListFoo, configListBar)
161+
})
162+
163+
Context("when testing connectivity", func() {
164+
It("should prevent communication within foo network", func() {
165+
err := ping(namespaces, results, 0, 1)
166+
Expect(err).To(HaveOccurred())
167+
err = ping(namespaces, results, 1, 0)
168+
Expect(err).To(HaveOccurred())
169+
})
170+
171+
It("should prevent communication between foo and bar networks", func() {
172+
err := ping(namespaces, results, 0, 2)
173+
Expect(err).To(HaveOccurred())
174+
err = ping(namespaces, results, 1, 2)
175+
Expect(err).To(HaveOccurred())
176+
err = ping(namespaces, results, 2, 0)
177+
Expect(err).To(HaveOccurred())
178+
err = ping(namespaces, results, 2, 1)
179+
Expect(err).To(HaveOccurred())
180+
})
202181
})
203182
})
204183
})
184+
185+
func setupNetworks(cniConf *libcni.CNIConfig, namespaces [nsCount]ns.NetNS,
186+
configListFoo, configListBar *libcni.NetworkConfigList,
187+
) [nsCount]*types100.Result {
188+
var results [nsCount]*types100.Result
189+
190+
for i := 0; i < nsCount; i++ {
191+
runtimeConfig := libcni.RuntimeConf{
192+
ContainerID: fmt.Sprintf("test-cni-firewall-%d", i),
193+
NetNS: namespaces[i].Path(),
194+
IfName: "eth0",
195+
}
196+
197+
configList := configListFoo
198+
if i >= 2 {
199+
configList = configListBar
200+
}
201+
202+
// Cleanup any existing network
203+
_ = cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
204+
205+
// Create network
206+
res, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
207+
Expect(err).NotTo(HaveOccurred())
208+
209+
// Setup cleanup
210+
DeferCleanup(func() {
211+
_ = cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
212+
})
213+
214+
results[i], err = types100.NewResultFromResult(res)
215+
Expect(err).NotTo(HaveOccurred())
216+
}
217+
218+
return results
219+
}
220+
221+
func ping(namespaces [nsCount]ns.NetNS, results [nsCount]*types100.Result, src, dst int) error {
222+
return namespaces[src].Do(func(ns.NetNS) error {
223+
defer GinkgoRecover()
224+
saddr := results[src].IPs[0].Address.IP.String()
225+
daddr := results[dst].IPs[0].Address.IP.String()
226+
srcNetName := results[src].Interfaces[0].Name
227+
dstNetName := results[dst].Interfaces[0].Name
228+
229+
fmt.Fprintf(GinkgoWriter, "ping %s (ns%d@%s) -> %s (ns%d@%s)...",
230+
saddr, src, srcNetName, daddr, dst, dstNetName)
231+
timeoutSec := 1
232+
if err := testutils.Ping(saddr, daddr, timeoutSec); err != nil {
233+
fmt.Fprintln(GinkgoWriter, "unpingable")
234+
return err
235+
}
236+
fmt.Fprintln(GinkgoWriter, "pingable")
237+
return nil
238+
})
239+
}

0 commit comments

Comments
 (0)