/
iptables.go
246 lines (205 loc) · 7.97 KB
/
iptables.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
package iptables
import (
"fmt"
"log"
"os/exec"
"strconv"
"strings"
"time"
)
const (
RedirectAllMode = "redirect-all"
RedirectListedMode = "redirect-listed"
IptablesPreroutingChainName = "PREROUTING"
IptablesOutputChainName = "OUTPUT"
)
var (
ExecutionTraceId = strconv.Itoa(int(time.Now().Unix()))
)
type FirewallConfiguration struct {
Mode string
PortsToRedirectInbound []int
InboundPortsToIgnore []int
OutboundPortsToIgnore []int
ProxyInboundPort int
ProxyOutgoingPort int
ProxyUid int
SimulateOnly bool
}
//ConfigureFirewall configures a pod's internal iptables to redirect all desired traffic through the proxy, allowing for
// the pod to join the service mesh. A lot of this logic was based on
// https://github.com/istio/istio/blob/e83411e/pilot/docker/prepare_proxy.sh
func ConfigureFirewall(firewallConfiguration FirewallConfiguration) error {
log.Printf("Tracing this script execution as [%s]\n", ExecutionTraceId)
log.Println("State of iptables rules before run:")
err := executeCommand(firewallConfiguration, makeShowAllRules())
if err != nil {
log.Println("Aborting firewall configuration")
return err
}
commands := make([]*exec.Cmd, 0)
commands = addIncomingTrafficRules(commands, firewallConfiguration)
commands = addOutgoingTrafficRules(commands, firewallConfiguration)
commands = append(commands, makeShowAllRules())
log.Println("Executing commands:")
for _, cmd := range commands {
err := executeCommand(firewallConfiguration, cmd)
if err != nil {
log.Println("Aborting firewall configuration")
return err
}
}
return nil
}
//formatComment is used to format iptables comments in such way that it is possible to identify when the rules were added.
// This helps debug when iptables has some stale rules from previous runs, something that can happen frequently on minikube.
func formatComment(text string) string {
return fmt.Sprintf("proxy-init/%s/%s", text, ExecutionTraceId)
}
func addOutgoingTrafficRules(commands []*exec.Cmd, firewallConfiguration FirewallConfiguration) []*exec.Cmd {
outputChainName := "PROXY_INIT_OUTPUT"
executeCommand(firewallConfiguration, makeFlushChain(outputChainName))
executeCommand(firewallConfiguration, makeDeleteChain(outputChainName))
commands = append(commands, makeCreateNewChain(outputChainName, "redirect-common-chain"))
// Ingore traffic from the proxy
if firewallConfiguration.ProxyUid > 0 {
log.Printf("Ignoring uid %d", firewallConfiguration.ProxyUid)
commands = append(commands, makeIgnoreUserId(outputChainName, firewallConfiguration.ProxyUid, "ignore-proxy-user-id"))
} else {
log.Println("Not ignoring any uid")
}
// Ignore loopback
commands = append(commands, makeIgnoreLoopback(outputChainName, "ignore-loopback"))
// Ignore ports
commands = addRulesForIgnoredPorts(firewallConfiguration.OutboundPortsToIgnore, outputChainName, commands)
log.Printf("Redirecting all OUTPUT to %d", firewallConfiguration.ProxyOutgoingPort)
commands = append(commands, makeRedirectChainToPort(outputChainName, firewallConfiguration.ProxyOutgoingPort, "redirect-all-outgoing-to-proxy-port"))
//Redirect all remaining outbound traffic to the proxy.
commands = append(commands, makeJumpFromChainToAnotherForAllProtocols(IptablesOutputChainName, outputChainName, "install-proxy-init-output"))
return commands
}
func addIncomingTrafficRules(commands []*exec.Cmd, firewallConfiguration FirewallConfiguration) []*exec.Cmd {
redirectChainName := "PROXY_INIT_REDIRECT"
executeCommand(firewallConfiguration, makeFlushChain(redirectChainName))
executeCommand(firewallConfiguration, makeDeleteChain(redirectChainName))
commands = append(commands, makeCreateNewChain(redirectChainName, "redirect-common-chain"))
commands = addRulesForIgnoredPorts(firewallConfiguration.InboundPortsToIgnore, redirectChainName, commands)
commands = addRulesForInboundPortRedirect(firewallConfiguration, redirectChainName, commands)
//Redirect all remaining inbound traffic to the proxy.
commands = append(commands, makeJumpFromChainToAnotherForAllProtocols(IptablesPreroutingChainName, redirectChainName, "install-proxy-init-prerouting"))
return commands
}
func addRulesForInboundPortRedirect(firewallConfiguration FirewallConfiguration, chainName string, commands []*exec.Cmd) []*exec.Cmd {
if firewallConfiguration.Mode == RedirectAllMode {
log.Print("Will redirect all INPUT ports to proxy")
//Create a new chain for redirecting inbound and outbound traffic to the proxy port.
commands = append(commands, makeRedirectChainToPort(chainName,
firewallConfiguration.ProxyInboundPort,
"redirect-all-incoming-to-proxy-port"))
} else if firewallConfiguration.Mode == RedirectListedMode {
log.Printf("Will redirect some INPUT ports to proxy: %v", firewallConfiguration.PortsToRedirectInbound)
for _, port := range firewallConfiguration.PortsToRedirectInbound {
commands = append(commands, makeRedirectChainToPortBasedOnDestinationPort(chainName,
port,
firewallConfiguration.ProxyInboundPort,
fmt.Sprintf("redirect-port-%d-to-proxy-port", port)))
}
}
return commands
}
func addRulesForIgnoredPorts(portsToIgnore []int, chainName string, commands []*exec.Cmd) []*exec.Cmd {
for _, ignoredPort := range portsToIgnore {
log.Printf("Will ignore port %d on chain %s", ignoredPort, chainName)
commands = append(commands, makeIgnorePort(chainName, ignoredPort, fmt.Sprintf("ignore-port-%d", ignoredPort)))
}
return commands
}
func executeCommand(firewallConfiguration FirewallConfiguration, cmd *exec.Cmd) error {
log.Printf("> %s", strings.Trim(fmt.Sprintf("%v", cmd.Args), "[]"))
if !firewallConfiguration.SimulateOnly {
out, err := cmd.CombinedOutput()
log.Printf("< %s\n", string(out))
if err != nil {
return err
}
}
return nil
}
func makeIgnoreUserId(chainName string, uid int, comment string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-A", chainName,
"-m", "owner",
"--uid-owner", strconv.Itoa(uid),
"-j", "RETURN",
"-m", "comment",
"--comment", formatComment(comment))
}
func makeCreateNewChain(name string, comment string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-N", name,
"-m", "comment",
"--comment", formatComment(comment))
}
func makeFlushChain(name string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-F", name)
}
func makeDeleteChain(name string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-X", name)
}
func makeRedirectChainToPort(chainName string, portToRedirect int, comment string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-A", chainName,
"-p", "tcp",
"-j", "REDIRECT",
"--to-port", strconv.Itoa(portToRedirect),
"-m", "comment",
"--comment", formatComment(comment))
}
func makeIgnorePort(chainName string, portToIgnore int, comment string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-A", chainName,
"-p", "tcp",
"--destination-port", strconv.Itoa(portToIgnore),
"-j", "RETURN",
"-m", "comment",
"--comment", formatComment(comment))
}
func makeIgnoreLoopback(chainName string, comment string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-A", chainName,
"-o", "lo",
"-j", "RETURN",
"-m", "comment",
"--comment", formatComment(comment))
}
func makeRedirectChainToPortBasedOnDestinationPort(chainName string, destinationPort int, portToRedirect int, comment string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-A", chainName,
"-p", "tcp",
"--destination-port", strconv.Itoa(destinationPort),
"-j", "REDIRECT",
"--to-port", strconv.Itoa(portToRedirect),
"-m", "comment",
"--comment", formatComment(comment))
}
func makeJumpFromChainToAnotherForAllProtocols(chainName string, targetChain string, comment string) *exec.Cmd {
return exec.Command("iptables",
"-t", "nat",
"-A", chainName,
"-j", targetChain,
"-m", "comment",
"--comment", formatComment(comment))
}
func makeShowAllRules() *exec.Cmd {
return exec.Command("iptables", "-t", "nat", "-vnL")
}