Skip to content

Commit 4aeab54

Browse files
author
Robert Poskevich III
committed
Implemented IPSEC manager for all exposed actions on the IpSec tunnel context servce. Wrapped with CLI functionality to list, configure, detail and update tunnel contexts, add and remove internal, remote and service subnets, and add, remove and update address translations
1 parent 84f84b0 commit 4aeab54

File tree

17 files changed

+958
-0
lines changed

17 files changed

+958
-0
lines changed

SoftLayer/CLI/custom_types.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
SoftLayer.CLI.custom_types
3+
~~~~~~~~~~~~~~~~~~~~~~~~
4+
Custom type declarations extending click.ParamType
5+
6+
:license: MIT, see LICENSE for more details.
7+
"""
8+
9+
import click
10+
11+
12+
class NetworkParamType(click.ParamType):
13+
"""Validates a network parameter type and converts to a tuple.
14+
15+
todo: Implement to ipaddress.ip_network once the ipaddress backport
16+
module can be added as a dependency or is available on all
17+
supported python versions.
18+
"""
19+
name = 'network'
20+
21+
def convert(self, value, param, ctx):
22+
try:
23+
# Inlined from python standard ipaddress module
24+
# https://docs.python.org/3/library/ipaddress.html
25+
address = str(value).split('/')
26+
if len(address) != 2:
27+
raise ValueError("Only one '/' permitted in %r" % value)
28+
29+
ip_address, cidr = address
30+
return (ip_address, int(cidr))
31+
except ValueError:
32+
self.fail('{} is not a valid network'.format(value), param, ctx)

SoftLayer/CLI/routes.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@
123123
('image:import', 'SoftLayer.CLI.image.import:cli'),
124124
('image:export', 'SoftLayer.CLI.image.export:cli'),
125125

126+
('ipsec', 'SoftLayer.CLI.vpn.ipsec'),
127+
('ipsec:configure', 'SoftLayer.CLI.vpn.ipsec.configure:cli'),
128+
('ipsec:detail', 'SoftLayer.CLI.vpn.ipsec.detail:cli'),
129+
('ipsec:list', 'SoftLayer.CLI.vpn.ipsec.list:cli'),
130+
('ipsec:subnet-add', 'SoftLayer.CLI.vpn.ipsec.subnet.add:cli'),
131+
('ipsec:subnet-remove', 'SoftLayer.CLI.vpn.ipsec.subnet.remove:cli'),
132+
('ipsec:translation-add', 'SoftLayer.CLI.vpn.ipsec.translation.add:cli'),
133+
('ipsec:translation-remove',
134+
'SoftLayer.CLI.vpn.ipsec.translation.remove:cli'),
135+
('ipsec:translation-update',
136+
'SoftLayer.CLI.vpn.ipsec.translation.update:cli'),
137+
('ipsec:update', 'SoftLayer.CLI.vpn.ipsec.update:cli'),
138+
126139
('loadbal', 'SoftLayer.CLI.loadbal'),
127140
('loadbal:cancel', 'SoftLayer.CLI.loadbal.cancel:cli'),
128141
('loadbal:create', 'SoftLayer.CLI.loadbal.create:cli'),

SoftLayer/CLI/vpn/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Virtual Private Networks"""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""IPSEC VPN"""
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Request network configuration of an IPSEC tunnel context."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI.exceptions import CLIHalt
9+
10+
11+
@click.command()
12+
@click.argument('context_id', type=int)
13+
@environment.pass_env
14+
def cli(env, context_id):
15+
"""Request configuration of a tunnel context.
16+
17+
This action will update the advancedConfigurationFlag on the context
18+
instance and further modifications against the context will be prevented
19+
until all changes can be propgated to network devices.
20+
"""
21+
manager = SoftLayer.IPSECManager(env.client)
22+
# ensure context can be retrieved by given id
23+
manager.get_tunnel_context(context_id)
24+
25+
succeeded = manager.apply_configuration(context_id)
26+
if succeeded:
27+
env.out('Configuration request received for context #{}'
28+
.format(context_id))
29+
else:
30+
raise CLIHalt('Failed to enqueue configuration request for context #{}'
31+
.format(context_id))

SoftLayer/CLI/vpn/ipsec/detail.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""List IPSEC VPN Tunnel Context Details."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import formatting
9+
10+
11+
@click.command()
12+
@click.argument('context_id', type=int)
13+
@click.option('-i',
14+
'--include',
15+
default=[],
16+
multiple=True,
17+
type=click.Choice(['at', 'is', 'rs', 'sr', 'ss']),
18+
help='Include additional resources')
19+
@environment.pass_env
20+
def cli(env, context_id, include):
21+
"""List IPSEC VPN tunnel context details.
22+
23+
Additional resources can be joined using multiple instances of the
24+
include option, for which the following choices are available.
25+
26+
\b
27+
at: address translations
28+
is: internal subnets
29+
rs: remote subnets
30+
sr: statically routed subnets
31+
ss: service subnets
32+
"""
33+
mask = _get_tunnel_context_mask(('at' in include),
34+
('is' in include),
35+
('rs' in include),
36+
('sr' in include),
37+
('ss' in include))
38+
manager = SoftLayer.IPSECManager(env.client)
39+
context = manager.get_tunnel_context(context_id, mask=mask)
40+
41+
env.out('Context Details:')
42+
env.fout(_get_context_table(context))
43+
44+
for relation in include:
45+
if relation == 'at':
46+
env.out('Address Translations:')
47+
env.fout(_get_address_translations_table(
48+
context.get('addressTranslations', [])))
49+
elif relation == 'is':
50+
env.out('Internal Subnets:')
51+
env.fout(_get_subnets_table(context.get('internalSubnets', [])))
52+
elif relation == 'rs':
53+
env.out('Remote Subnets:')
54+
env.fout(_get_subnets_table(context.get('customerSubnets', [])))
55+
elif relation == 'sr':
56+
env.out('Static Subnets:')
57+
env.fout(_get_subnets_table(context.get('staticRouteSubnets', [])))
58+
elif relation == 'ss':
59+
env.out('Service Subnets:')
60+
env.fout(_get_subnets_table(context.get('serviceSubnets', [])))
61+
62+
63+
def _get_address_translations_table(address_translations):
64+
"""Yields a formatted table to print address translations.
65+
66+
:param List[dict] address_translations: List of address translations.
67+
:return Table: Formatted for address translation output.
68+
"""
69+
table = formatting.Table(['id',
70+
'static IP address',
71+
'static IP address id',
72+
'remote IP address',
73+
'remote IP address id',
74+
'note'])
75+
for address_translation in address_translations:
76+
table.add_row([address_translation.get('id', ''),
77+
address_translation.get('internalIpAddressRecord', {})
78+
.get('ipAddress', ''),
79+
address_translation.get('internalIpAddressId', ''),
80+
address_translation.get('customerIpAddressRecord', {})
81+
.get('ipAddress', ''),
82+
address_translation.get('customerIpAddressId', ''),
83+
address_translation.get('notes', '')])
84+
return table
85+
86+
87+
def _get_subnets_table(subnets):
88+
"""Yields a formatted table to print subnet details.
89+
90+
:param List[dict] subnets: List of subnets.
91+
:return Table: Formatted for subnet output.
92+
"""
93+
table = formatting.Table(['id',
94+
'network identifier',
95+
'cidr',
96+
'note'])
97+
for subnet in subnets:
98+
table.add_row([subnet.get('id', ''),
99+
subnet.get('networkIdentifier', ''),
100+
subnet.get('cidr', ''),
101+
subnet.get('note', '')])
102+
return table
103+
104+
105+
def _get_tunnel_context_mask(address_translations=False,
106+
internal_subnets=False,
107+
remote_subnets=False,
108+
static_subnets=False,
109+
service_subnets=False):
110+
"""Yields a mask object for a tunnel context.
111+
112+
All exposed properties on the tunnel context service are included in
113+
the constructed mask. Additional joins may be requested.
114+
115+
:param bool address_translations: Whether to join the context's address
116+
translation entries.
117+
:param bool internal_subnets: Whether to join the context's internal
118+
subnet associations.
119+
:param bool remote_subnets: Whether to join the context's remote subnet
120+
associations.
121+
:param bool static_subnets: Whether to join the context's statically
122+
routed subnet associations.
123+
:param bool service_subnets: Whether to join the SoftLayer service
124+
network subnets.
125+
:return string: Encoding for the requested mask object.
126+
"""
127+
entries = ['id',
128+
'accountId',
129+
'advancedConfigurationFlag',
130+
'createDate',
131+
'customerPeerIpAddress',
132+
'modifyDate',
133+
'name',
134+
'friendlyName',
135+
'internalPeerIpAddress',
136+
'phaseOneAuthentication',
137+
'phaseOneDiffieHellmanGroup',
138+
'phaseOneEncryption',
139+
'phaseOneKeylife',
140+
'phaseTwoAuthentication',
141+
'phaseTwoDiffieHellmanGroup',
142+
'phaseTwoEncryption',
143+
'phaseTwoKeylife',
144+
'phaseTwoPerfectForwardSecrecy',
145+
'presharedKey']
146+
if address_translations:
147+
entries.append('addressTranslations[internalIpAddressRecord[ipAddress],'
148+
'customerIpAddressRecord[ipAddress]]')
149+
if internal_subnets:
150+
entries.append('internalSubnets')
151+
if remote_subnets:
152+
entries.append('customerSubnets')
153+
if static_subnets:
154+
entries.append('staticRouteSubnets')
155+
if service_subnets:
156+
entries.append('serviceSubnets')
157+
return '[mask[{}]]'.format(','.join(entries))
158+
159+
160+
def _get_context_table(context):
161+
"""Yields a formatted table to print context details.
162+
163+
:param dict context: The tunnel context
164+
:return Table: Formatted for tunnel context output
165+
"""
166+
table = formatting.KeyValueTable(['name', 'value'])
167+
table.align['name'] = 'r'
168+
table.align['value'] = 'l'
169+
170+
table.add_row(['id', context.get('id', '')])
171+
table.add_row(['name', context.get('name', '')])
172+
table.add_row(['friendly name', context.get('friendlyName', '')])
173+
table.add_row(['internal peer IP address',
174+
context.get('internalPeerIpAddress', '')])
175+
table.add_row(['remote peer IP address',
176+
context.get('customerPeerIpAddress', '')])
177+
table.add_row(['advanced configuration flag',
178+
context.get('advancedConfigurationFlag', '')])
179+
table.add_row(['preshared key', context.get('presharedKey', '')])
180+
table.add_row(['phase 1 authentication',
181+
context.get('phaseOneAuthentication', '')])
182+
table.add_row(['phase 1 diffie hellman group',
183+
context.get('phaseOneDiffieHellmanGroup', '')])
184+
table.add_row(['phase 1 encryption', context.get('phaseOneEncryption', '')])
185+
table.add_row(['phase 1 key life', context.get('phaseOneKeylife', '')])
186+
table.add_row(['phase 2 authentication',
187+
context.get('phaseTwoAuthentication', '')])
188+
table.add_row(['phase 2 diffie hellman group',
189+
context.get('phaseTwoDiffieHellmanGroup', '')])
190+
table.add_row(['phase 2 encryption', context.get('phaseTwoEncryption', '')])
191+
table.add_row(['phase 2 key life', context.get('phaseTwoKeylife', '')])
192+
table.add_row(['phase 2 perfect forward secrecy',
193+
context.get('phaseTwoPerfectForwardSecrecy', '')])
194+
table.add_row(['created', context.get('createDate')])
195+
table.add_row(['modified', context.get('modifyDate')])
196+
return table

SoftLayer/CLI/vpn/ipsec/list.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""List IPSec VPN Tunnel Contexts."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import environment
8+
from SoftLayer.CLI import formatting
9+
10+
11+
@click.command()
12+
@environment.pass_env
13+
def cli(env):
14+
"""List IPSec VPN tunnel contexts"""
15+
manager = SoftLayer.IPSECManager(env.client)
16+
contexts = manager.get_tunnel_contexts()
17+
18+
table = formatting.Table(['id',
19+
'name',
20+
'friendly name',
21+
'internal peer IP address',
22+
'remote peer IP address',
23+
'created'])
24+
for context in contexts:
25+
table.add_row([context.get('id', ''),
26+
context.get('name', ''),
27+
context.get('friendlyName', ''),
28+
context.get('internalPeerIpAddress', ''),
29+
context.get('customerPeerIpAddress', ''),
30+
context.get('createDate', '')])
31+
env.fout(table)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""IPSEC VPN Subnets"""

0 commit comments

Comments
 (0)