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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""Security Group Rule action implementations"""
import six
try:
from novaclient.v2 import security_group_rules as compute_secgroup_rules
except ImportError:
from novaclient.v1_1 import security_group_rules as compute_secgroup_rules
from openstackclient.common import exceptions
from openstackclient.common import parseractions
from openstackclient.common import utils
from openstackclient.network import common
from openstackclient.network import utils as network_utils
def _format_security_group_rule_show(obj):
data = network_utils.transform_compute_security_group_rule(obj)
return zip(*sorted(six.iteritems(data)))
def _format_network_port_range(rule):
port_range = ''
if (rule.protocol != 'icmp' and
(rule.port_range_min or rule.port_range_max)):
port_range_min = str(rule.port_range_min)
port_range_max = str(rule.port_range_max)
if rule.port_range_min is None:
port_range_min = port_range_max
if rule.port_range_max is None:
port_range_max = port_range_min
port_range = port_range_min + ':' + port_range_max
return port_range
def _get_columns(item):
columns = list(item.keys())
if 'tenant_id' in columns:
columns.remove('tenant_id')
columns.append('project_id')
return tuple(sorted(columns))
def _convert_to_lowercase(string):
return string.lower()
class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
"""Create a new security group rule"""
def update_parser_common(self, parser):
parser.add_argument(
'group',
metavar='<group>',
help='Create rule in this security group (name or ID)',
)
# TODO(rtheis): Add support for additional protocols for network.
# Until then, continue enforcing the compute choices.
parser.add_argument(
"--proto",
metavar="<proto>",
default="tcp",
choices=['icmp', 'tcp', 'udp'],
type=_convert_to_lowercase,
help="IP protocol (icmp, tcp, udp; default: tcp)",
)
source_group = parser.add_mutually_exclusive_group()
source_group.add_argument(
"--src-ip",
metavar="<ip-address>",
default="0.0.0.0/0",
help="Source IP address block (may use CIDR notation; default: "
"0.0.0.0/0)",
)
source_group.add_argument(
"--src-group",
metavar="<group>",
help="Source security group (name or ID)",
)
parser.add_argument(
"--dst-port",
metavar="<port-range>",
default=(0, 0),
action=parseractions.RangeAction,
help="Destination port, may be a single port or port range: "
"137:139 (only required for IP protocols tcp and udp)",
)
return parser
def take_action_network(self, client, parsed_args):
# Get the security group ID to hold the rule.
security_group_id = client.find_security_group(
parsed_args.group,
ignore_missing=False
).id
# Build the create attributes.
attrs = {}
# TODO(rtheis): Add --direction option. Until then, continue
# with the default of 'ingress'.
attrs['direction'] = 'ingress'
# TODO(rtheis): Add --ethertype option. Until then, continue
# with the default of 'IPv4'
attrs['ethertype'] = 'IPv4'
# TODO(rtheis): Add port range support (type and code) for icmp
# protocol. Until then, continue ignoring the port range.
if parsed_args.proto != 'icmp':
attrs['port_range_min'] = parsed_args.dst_port[0]
attrs['port_range_max'] = parsed_args.dst_port[1]
attrs['protocol'] = parsed_args.proto
if parsed_args.src_group is not None:
attrs['remote_group_id'] = client.find_security_group(
parsed_args.src_group,
ignore_missing=False
).id
else:
attrs['remote_ip_prefix'] = parsed_args.src_ip
attrs['security_group_id'] = security_group_id
# Create and show the security group rule.
obj = client.create_security_group_rule(**attrs)
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
return (columns, data)
def take_action_compute(self, client, parsed_args):
group = utils.find_resource(
client.security_groups,
parsed_args.group,
)
if parsed_args.proto == 'icmp':
from_port, to_port = -1, -1
else:
from_port, to_port = parsed_args.dst_port
if parsed_args.src_group is not None:
parsed_args.src_group = utils.find_resource(
client.security_groups,
parsed_args.src_group,
).id
obj = client.security_group_rules.create(
group.id,
parsed_args.proto,
from_port,
to_port,
parsed_args.src_ip,
parsed_args.src_group,
)
return _format_security_group_rule_show(obj._info)
class DeleteSecurityGroupRule(common.NetworkAndComputeCommand):
"""Delete a security group rule"""
def update_parser_common(self, parser):
parser.add_argument(
'rule',
metavar='<rule>',
help='Security group rule to delete (ID only)',
)
return parser
def take_action_network(self, client, parsed_args):
obj = client.find_security_group_rule(parsed_args.rule)
client.delete_security_group_rule(obj)
def take_action_compute(self, client, parsed_args):
client.security_group_rules.delete(parsed_args.rule)
class ListSecurityGroupRule(common.NetworkAndComputeLister):
"""List security group rules"""
def update_parser_common(self, parser):
parser.add_argument(
'group',
metavar='<group>',
nargs='?',
help='List all rules in this security group (name or ID)',
)
return parser
def _get_column_headers(self, parsed_args):
column_headers = (
'ID',
'IP Protocol',
'IP Range',
'Port Range',
'Remote Security Group',
)
if parsed_args.group is None:
column_headers = column_headers + ('Security Group',)
return column_headers
def take_action_network(self, client, parsed_args):
column_headers = self._get_column_headers(parsed_args)
columns = (
'id',
'protocol',
'remote_ip_prefix',
'port_range_min',
'remote_group_id',
)
# Get the security group rules using the requested query.
query = {}
if parsed_args.group is not None:
# NOTE(rtheis): Unfortunately, the security group resource
# does not contain security group rules resources. So use
# the security group ID in a query to get the resources.
security_group_id = client.find_security_group(
parsed_args.group,
ignore_missing=False
).id
query = {'security_group_id': security_group_id}
else:
columns = columns + ('security_group_id',)
rules = list(client.security_group_rules(**query))
# Reformat the rules to display a port range instead
# of just the port range minimum. This maintains
# output compatibility with compute.
for rule in rules:
rule.port_range_min = _format_network_port_range(rule)
return (column_headers,
(utils.get_item_properties(
s, columns,
) for s in rules))
def take_action_compute(self, client, parsed_args):
column_headers = self._get_column_headers(parsed_args)
columns = (
"ID",
"IP Protocol",
"IP Range",
"Port Range",
"Remote Security Group",
)
rules_to_list = []
if parsed_args.group is not None:
group = utils.find_resource(
client.security_groups,
parsed_args.group,
)
rules_to_list = group.rules
else:
columns = columns + ('parent_group_id',)
for group in client.security_groups.list():
rules_to_list.extend(group.rules)
# NOTE(rtheis): Turn the raw rules into resources.
rules = []
for rule in rules_to_list:
rules.append(compute_secgroup_rules.SecurityGroupRule(
client.security_group_rules,
network_utils.transform_compute_security_group_rule(rule),
))
return (column_headers,
(utils.get_item_properties(
s, columns,
) for s in rules))
class ShowSecurityGroupRule(common.NetworkAndComputeShowOne):
"""Display security group rule details"""
def update_parser_common(self, parser):
parser.add_argument(
'rule',
metavar="<rule>",
help="Security group rule to display (ID only)"
)
return parser
def take_action_network(self, client, parsed_args):
obj = client.find_security_group_rule(parsed_args.rule,
ignore_missing=False)
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns)
return (columns, data)
def take_action_compute(self, client, parsed_args):
# NOTE(rtheis): Unfortunately, compute does not have an API
# to get or list security group rules so parse through the
# security groups to find all accessible rules in search of
# the requested rule.
obj = None
security_group_rules = []
for security_group in client.security_groups.list():
security_group_rules.extend(security_group.rules)
for security_group_rule in security_group_rules:
if parsed_args.rule == str(security_group_rule.get('id')):
obj = security_group_rule
break
if obj is None:
msg = "Could not find security group rule " \
"with ID %s" % parsed_args.rule
raise exceptions.CommandError(msg)
# NOTE(rtheis): Format security group rule
return _format_security_group_rule_show(obj)
|