Skip to content

Commit 72cfb69

Browse files
abhindesCapirca Team
authored andcommitted
Adding 'mixed' support to gce generator.
PiperOrigin-RevId: 378304064
1 parent 0b36b02 commit 72cfb69

5 files changed

Lines changed: 458 additions & 19 deletions

File tree

capirca/lib/gce.py

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ def IsDefaultDeny(term):
7474
return True
7575

7676

77+
78+
def GetNextPriority(priority):
79+
"""Get the priority for the next rule."""
80+
return priority
81+
82+
7783
class Term(gcp.Term):
7884
"""Creates the term for the GCE firewall."""
7985

@@ -104,10 +110,14 @@ class Term(gcp.Term):
104110
# Any protocol not in _ALLOW_PROTO_NAME must be passed by number.
105111
ALWAYS_PROTO_NUM = set(gcp.Term.PROTO_MAP.keys()) - _ALLOW_PROTO_NAME
106112

107-
def __init__(self, term, inet_version='inet'):
113+
def __init__(self, term, inet_version='inet', policy_inet_version='inet'):
108114
super(Term, self).__init__(term)
109115
self.term = term
110116
self.inet_version = inet_version
117+
# This is to handle mixed, where the policy_inet_version is mixed,
118+
# but the term inet version is either inet/inet6.
119+
# This is only useful for term name and priority.
120+
self.policy_inet_version = policy_inet_version
111121

112122
self._validateDirection()
113123
if self.term.source_address_exclude and not self.term.source_address:
@@ -194,6 +204,13 @@ def ConvertToDict(self):
194204
term_dict['network'] = self.term.network
195205
term_dict['name'] = '%s-%s' % (
196206
self.term.network.split('/')[-1], term_dict['name'])
207+
# Identify if this is inet6 processing for a term under a mixed policy.
208+
mixed_policy_inet6_term = False
209+
if self.policy_inet_version == 'mixed' and self.inet_version == 'inet6':
210+
mixed_policy_inet6_term = True
211+
# Update term name to have the IPv6 suffix for the inet6 rule.
212+
if mixed_policy_inet6_term:
213+
term_dict['name'] = gcp.GetIpv6TermName(term_dict['name'])
197214

198215
# Checking counts of tags, and ports to see if they exceeded limits.
199216
if len(self.term.source_tag) > self._TERM_SOURCE_TAGS_LIMIT:
@@ -214,19 +231,35 @@ def ConvertToDict(self):
214231
term_dict['targetTags'] = self.term.destination_tag
215232
if self.term.priority:
216233
term_dict['priority'] = self.term.priority
234+
# Update term priority for the inet6 rule.
235+
if mixed_policy_inet6_term:
236+
term_dict['priority'] = GetNextPriority(term_dict['priority'])
217237

218238
rules = []
219-
# TODO(abhindes) correctly account for 'mixed' as well
239+
# If 'mixed' ends up in indvidual term inet_version, something has gone
240+
# horribly wrong. The only valid values are inet/inet6.
220241
term_af = self.AF_MAP.get(self.inet_version)
242+
if self.inet_version == 'mixed':
243+
raise GceFirewallError(
244+
'GCE firewall rule has incorrect inet_version for rule: %s' %
245+
self.term.name)
246+
247+
# Exit early for inet6 processing of mixed rules that have only tags,
248+
# and no IP addresses, since this is handled in the inet processing.
249+
if mixed_policy_inet6_term:
250+
if not self.term.source_address and not self.term.destination_address:
251+
if 'targetTags' in term_dict or 'sourceTags' in term_dict:
252+
return []
253+
221254
saddrs = sorted(self.term.GetAddressOfVersion('source_address', term_af),
222255
key=ipaddress.get_mixed_type_key)
223256
daddrs = sorted(
224257
self.term.GetAddressOfVersion('destination_address', term_af),
225258
key=ipaddress.get_mixed_type_key)
226259

227260
# If the address got filtered out and is empty due to address family, we
228-
# don't render the term. At this point of term processing, the direction has
229-
# already been validated, so we can just log and return empty rule.
261+
# don't render the term. At this point of term processing, the direction
262+
# has already been validated, so we can just log and return empty rule.
230263
if self.term.source_address and not saddrs:
231264
logging.warning(
232265
'WARNING: Term %s is not being rendered for %s, '
@@ -252,7 +285,7 @@ def ConvertToDict(self):
252285
filtered_protocols = []
253286
for proto in self.term.protocol:
254287
# ICMP filtering by inet_version
255-
# TODO(abhindes) deal with "mixed" correctly
288+
# Since each term has inet_version, 'mixed' is correctly processed here.
256289
# Convert protocol to number for uniformity of comparison.
257290
# PROTO_MAP always returns protocol number.
258291
if proto in self._ALLOW_PROTO_NAME:
@@ -349,7 +382,7 @@ class GCE(gcp.GCP):
349382

350383
_PLATFORM = 'gce'
351384
SUFFIX = '.gce'
352-
_SUPPORTED_AF = frozenset(('inet', 'inet6'))
385+
_SUPPORTED_AF = frozenset(('inet', 'inet6', 'mixed'))
353386
_ANY_IP = {
354387
'inet': nacaddr.IP('0.0.0.0/0'),
355388
'inet6': nacaddr.IP('::/0'),
@@ -438,10 +471,19 @@ def _TranslatePolicy(self, pol, exp_info):
438471
terms[-1].priority = 65534
439472
if direction == 'EGRESS':
440473
if address_family != 'mixed':
474+
# Default deny also gets processed as part of terms processing.
475+
# The name and priority get updated there.
441476
terms[-1].destination_address = [self._ANY_IP[address_family]]
477+
else:
478+
terms[-1].destination_address = [self._ANY_IP['inet'],
479+
self._ANY_IP['inet6']]
442480
else:
443481
if address_family != 'mixed':
444482
terms[-1].source_address = [self._ANY_IP[address_family]]
483+
else:
484+
terms[-1].source_address = [
485+
self._ANY_IP['inet'], self._ANY_IP['inet6']
486+
]
445487

446488
for term in terms:
447489
if term.stateless_reply:
@@ -456,7 +498,7 @@ def _TranslatePolicy(self, pol, exp_info):
456498
term.name += '-e'
457499
term.name = self.FixTermLength(term.name)
458500
if term.name in term_names:
459-
raise GceFirewallError('Duplicate term name')
501+
raise GceFirewallError('Duplicate term name %s' % term.name)
460502
term_names.add(term.name)
461503

462504
term.direction = direction
@@ -472,18 +514,26 @@ def _TranslatePolicy(self, pol, exp_info):
472514
raise GceFirewallError(
473515
'GCE firewall does not support term options.')
474516

475-
for rules in Term(term, address_family).ConvertToDict():
476-
logging.debug('Attribute count of rule %s is: %d', term.name,
477-
GetAttributeCount(rules))
478-
total_attribute_count += GetAttributeCount(rules)
479-
total_rule_count += 1
480-
if max_attribute_count and total_attribute_count > max_attribute_count:
481-
# Stop processing rules as soon as the attribute count is over the
482-
# limit.
483-
raise ExceededAttributeCountError(
484-
'Attribute count (%d) for %s exceeded the maximum (%d)' % (
485-
total_attribute_count, filter_name, max_attribute_count))
486-
self.gce_policies.append(rules)
517+
# Handle mixed for each indvidual term as inet and inet6.
518+
# inet/inet6 are treated the same.
519+
term_address_families = []
520+
if address_family == 'mixed':
521+
term_address_families = ['inet', 'inet6']
522+
else:
523+
term_address_families = [address_family]
524+
for term_af in term_address_families:
525+
for rules in Term(term, term_af, address_family).ConvertToDict():
526+
logging.debug('Attribute count of rule %s is: %d', term.name,
527+
GetAttributeCount(rules))
528+
total_attribute_count += GetAttributeCount(rules)
529+
total_rule_count += 1
530+
if max_attribute_count and total_attribute_count > max_attribute_count:
531+
# Stop processing rules as soon as the attribute count is over the
532+
# limit.
533+
raise ExceededAttributeCountError(
534+
'Attribute count (%d) for %s exceeded the maximum (%d)' %
535+
(total_attribute_count, filter_name, max_attribute_count))
536+
self.gce_policies.append(rules)
487537
logging.info('Total rule count of policy %s is: %d', filter_name,
488538
total_rule_count)
489539
logging.info('Total attribute count of policy %s is: %d', filter_name,

capirca/lib/gcp.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,17 @@ def TruncateString(raw_string, max_length):
150150
if len(raw_string) > max_length:
151151
return raw_string[:max_length]
152152
return raw_string
153+
154+
155+
def GetIpv6TermName(term_name):
156+
"""Returns the equivalent term name for IPv6 terms.
157+
158+
Args:
159+
term_name: A string.
160+
161+
Returns:
162+
string: The IPv6 requivalent term name.
163+
"""
164+
165+
return '%s-%s' % (term_name, 'v6')
166+

policies/pol/sample_mixed_gce.pol

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#
2+
# This is an example inet6 (i.e IPv6) policy for capirca
3+
# Target defaults to INGRESS is not specified in the header
4+
#
5+
header {
6+
comment:: "this is a sample policy to generate GCE filter"
7+
target:: gce global/networks/default mixed
8+
}
9+
10+
term test-ssh-mixed {
11+
comment:: "Allow SSH access from Server and Company with mixed addresses."
12+
source-address:: PUBLIC_IPV6_SERVERS PUBLIC_NAT
13+
protocol:: tcp
14+
destination-port:: SSH
15+
action:: accept
16+
}
17+
18+
term test-ssh-v6 {
19+
comment:: "Allow SSH access from IPv6 server."
20+
source-address:: PUBLIC_IPV6_SERVERS
21+
protocol:: tcp
22+
destination-port:: SSH
23+
action:: accept
24+
}
25+
26+
term test-ssh-v4 {
27+
comment:: "Allow SSH access to all instances from company."
28+
source-address:: PUBLIC_NAT
29+
protocol:: tcp
30+
destination-port:: SSH
31+
action:: accept
32+
}
33+
34+
term test-multiple-protocols {
35+
comment:: "Allow high port access from a public IPv6 server."
36+
source-address:: PUBLIC_IPV6_SERVERS
37+
protocol:: tcp udp
38+
destination-port:: HIGH_PORTS
39+
action:: accept
40+
}
41+
42+
term test-multiple-protocols-tcp-icmpv6 {
43+
comment:: "Allow all tcp and icmpv6 from IPv6 Server."
44+
source-address:: PUBLIC_IPV6_SERVERS PUBLIC_NAT
45+
protocol:: tcp icmpv6
46+
action:: accept
47+
}
48+
49+
term test-multiple-protocols-tcp-icmp {
50+
comment:: "Allow all tcp and icmp."
51+
source-address:: PUBLIC_IPV6_SERVERS PUBLIC_NAT
52+
protocol:: tcp icmp
53+
action:: accept
54+
}
55+
56+
term test-multiple-protocols-tcp-icmpv6-v6-only {
57+
comment:: "Allow all tcp and icmpv6."
58+
source-address:: PUBLIC_IPV6_SERVERS
59+
protocol:: tcp icmpv6
60+
action:: accept
61+
}
62+
63+
term test-multiple-protocols-tcp-icmp-v4-only {
64+
comment:: "Allow all tcp and icmp."
65+
source-address:: PUBLIC_NAT
66+
protocol:: tcp icmp
67+
action:: accept
68+
}
69+
70+
term test-web {
71+
comment:: "Allow HTTP/S to instances with webserver tag and any IPs."
72+
source-tag:: webserver
73+
source-address:: ANY_MIXED
74+
protocol:: tcp
75+
destination-port:: HTTP
76+
destination-tag:: other-webserver
77+
action:: accept
78+
}
79+
80+
term test-web-tag-only {
81+
comment:: "Allow HTTP/S to instances with webserver tag only."
82+
source-tag:: webserver
83+
protocol:: tcp
84+
destination-port:: HTTP
85+
destination-tag:: other-webserver
86+
action:: accept
87+
}
88+
89+
term test-web-tag-v4-only {
90+
comment:: "Allow HTTP/S to instances with webserver tag."
91+
source-address:: ANY
92+
source-tag:: webserver
93+
protocol:: tcp
94+
destination-port:: HTTP
95+
destination-tag:: other-webserver
96+
action:: accept
97+
}
98+
99+
term test-icmp {
100+
comment:: "Allow ICMP from company."
101+
source-address:: PUBLIC_NAT
102+
protocol:: icmp
103+
action:: accept
104+
}
105+
106+
term test-icmpv6 {
107+
comment:: "Allow ICMPv6 from IPv6 server."
108+
source-address:: PUBLIC_IPV6_SERVERS
109+
protocol:: icmpv6
110+
action:: accept
111+
}
112+
113+
term test-igmp {
114+
comment:: "Allow IGMP from server and company with mixed addresses."
115+
source-address:: PUBLIC_IPV6_SERVERS PUBLIC_NAT
116+
protocol:: igmp
117+
action:: accept
118+
}
119+
120+
term default-deny {
121+
action:: deny
122+
}
123+
124+
#
125+
# Sample EGRESS policy
126+
# If source-tag is included, it maps to targetTags in the GCP Egress rule
127+
#
128+
header {
129+
comment:: "this is a sample policy to generate EGRESS GCE filter"
130+
target:: gce EGRESS global/networks/default mixed
131+
}
132+
133+
term test-egress-address {
134+
comment:: "Outbound to Server with mixed addresses."
135+
protocol:: tcp
136+
destination-port:: SMTP
137+
destination-address:: PUBLIC_IPV6_SERVERS PUBLIC_NAT
138+
action:: accept
139+
}
140+
141+
term test-egress-tag {
142+
comment:: "Outbound to Server with tag."
143+
protocol:: tcp
144+
destination-port:: SSH
145+
destination-address:: PUBLIC_IPV6_SERVERS PUBLIC_NAT
146+
source-tag:: webserver
147+
action:: accept
148+
}
149+
150+
term test-egress-tag-v4-only {
151+
comment:: "Outbound to RFC1918."
152+
protocol:: tcp
153+
destination-port:: SSH
154+
destination-address:: RFC1918
155+
source-tag:: webserver
156+
action:: accept
157+
}
158+
159+
term test-egress-tag-v6-only {
160+
comment:: "Outbound to IPv6 Server."
161+
protocol:: tcp
162+
destination-port:: SSH
163+
destination-address:: PUBLIC_IPV6_SERVERS
164+
source-tag:: webserver
165+
action:: accept
166+
}
167+
168+
term egress-default-deny {
169+
action:: deny
170+
}

0 commit comments

Comments
 (0)