Skip to content

Commit cb58f69

Browse files
Merge pull request #824 from rposky/ipsec
IPsec API Support and CLI Interaction
2 parents 84f84b0 + 2a8f915 commit cb58f69

File tree

24 files changed

+2030
-5
lines changed

24 files changed

+2030
-5
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)