Skip to content

Commit 9696314

Browse files
authored
Saimon/upgrade depdencies to resolve CVE (#327)
1 parent 2814005 commit 9696314

File tree

14 files changed

+224
-150
lines changed

14 files changed

+224
-150
lines changed

cterasdk/common/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .item import Item # noqa: E402, F401
22
from .object import Object, Device, delete_attrs # noqa: E402, F401
3-
from .datetime_utils import DateTimeUtils, from_iso_format # noqa: E402, F401
4-
from .utils import merge, union, parse_base_object_ref, convert_size, df_military_time, DataUnit, parse_to_ipaddress, \
3+
from .datetime_utils import DateTimeUtils # noqa: E402, F401
4+
from .utils import merge, union, parse_base_object_ref, convert_size, df_military_time, DataUnit, \
55
utf8_decode, utf8_encode, Version # noqa: E402, F401
66
from .types import PolicyRule, PolicyRuleConverter, StringCriteriaBuilder, IntegerCriteriaBuilder, DateTimeCriteriaBuilder, \
77
PredefinedListCriteriaBuilder, CustomListCriteriaBuilder, ThrottlingRuleBuilder, ThrottlingRule, FilterBackupSet, \

cterasdk/common/datetime_utils.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,3 @@ def get_expiration_date(expiration):
1919
elif isinstance(expiration, datetime.date):
2020
expiration_date = expiration
2121
return expiration_date # pylint: disable=possibly-used-before-assignment
22-
23-
24-
def from_iso_format(time):
25-
"""
26-
Parse datetime object from ISO 8601 format
27-
28-
:param str time: Timestamp
29-
:returns: Datetime object
30-
:rtype: datetime.datetime
31-
"""
32-
return datetime.datetime.fromisoformat(time)

cterasdk/common/utils.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import re
22
import socket
33
import logging
4-
import ipaddress
54

65
from datetime import datetime
76
from packaging.version import parse as parse_version
@@ -154,28 +153,6 @@ def parse_base_object_ref(base_object_ref):
154153
return BaseObjectRef(**arguments)
155154

156155

157-
def parse_to_ipaddress(address):
158-
"""
159-
Parse an ip or network address into ipaddress object
160-
161-
:param str address: ip (10.0.0.5) or network address (192.168.44.0/28)
162-
:return: ipaddress.IPV4Address/IPV6Address or ipaddress.IPV4Network/IPV6Network
163-
"""
164-
try:
165-
try:
166-
ip_addrr = ipaddress.ip_address(address)
167-
logger.debug('ip address validated. %s', {'ip': str(ip_addrr)})
168-
return ip_addrr
169-
except (ValueError, TypeError):
170-
ip_network = ipaddress.ip_network(address)
171-
logger.debug('ip network validated. %s', {'network': str(ip_network)})
172-
return ip_network
173-
except (ValueError, TypeError):
174-
err = ValueError(f'{address} does not appear to be an IPv4 or IPv6 network or ip address')
175-
logger.error('Incorrect entry, please use IPv4 or IPv6 CIDR Formats. %s', {'Error': err})
176-
raise err
177-
178-
179156
class Version:
180157
"""Software Version"""
181158

@@ -222,10 +199,11 @@ def utf8_encode(message):
222199
return message.encode('utf-8')
223200

224201

225-
def tcp_connect(host, port):
202+
def tcp_connect(host, port, *, timeout=None):
226203
logger.debug('Testing connection. %s', {'host': host, 'port': port})
227204
message = f"Connection error to remote host {host} on port {port}."
228205
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
206+
sock.settimeout(timeout)
229207
rc = None
230208
try:
231209
rc = sock.connect_ex((host, port))

cterasdk/direct/cli.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import sys
2+
import asyncio
3+
import logging
4+
import argparse
5+
import aiofiles
6+
import yarl
7+
8+
9+
from .. import settings
10+
from ..common import utils
11+
from ..lib.storage import commonfs
12+
from .client import DirectIO
13+
from ..exceptions.direct import StreamError, DirectIOError
14+
from ..exceptions.transport import TLSError
15+
16+
17+
logging.basicConfig(format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", level=logging.ERROR)
18+
19+
20+
logger = logging.getLogger('cterasdk.direct')
21+
22+
23+
def validate_endpoint(endpoint):
24+
baseurl, port = yarl.URL(endpoint), 443
25+
logger.debug('Validating connection to host: %s on port: %s', baseurl.host, port)
26+
utils.tcp_connect(baseurl.host, port, timeout=5)
27+
return f'{baseurl}'
28+
29+
30+
def validate_directory_and_filename(path):
31+
directory, filename = commonfs.generate_file_destination(path)
32+
assert all([directory, filename]), f'Error: Could not resolve file path: {path}'
33+
34+
35+
async def download_from_object_storage(options, file_id, path):
36+
async with DirectIO(**options) as client:
37+
streamer = await client.streamer(file_id)
38+
try:
39+
async with aiofiles.open(path, 'wb') as fd:
40+
async for block in streamer.start():
41+
await fd.seek(block.offset)
42+
await fd.write(block.data)
43+
except StreamError as e:
44+
print(f'Download failed. Cause: {e.__cause__}', file=sys.stderr)
45+
46+
47+
def download_object():
48+
parser = argparse.ArgumentParser(
49+
description="Download a file from CTERA Portal via CTERA Direct I/O."
50+
)
51+
52+
arguments = [
53+
("--endpoint", "-e", {"type": str, "required": True, "help": "CTERA Portal (e.g. corp.acme.ctera.com)"}),
54+
("--path", "-p", {"type": str, "required": True, "help": "File path (e.g. ./download.zip)"}),
55+
("--access", "-a", {"type": str, "help": "Access Key (optional)"}),
56+
("--secret", "-s", {"type": str, "help": "Secret Key (optional)"}),
57+
("--bearer", "-b", {"type": str, "help": "Bearer token (optional)"}),
58+
("--file-id", "-f", {"required": True, "type": int, "help": "File ID (numeric)"}),
59+
("--no-verify-ssl", "-k", {"action": "store_true", "help": "Disable SSL verification"}),
60+
("--debug", "-d", {"action": "store_true", "help": "Enable debug logging"}),
61+
]
62+
63+
for lopt, sopt, options in arguments:
64+
parser.add_argument(lopt, sopt, **options)
65+
66+
args = parser.parse_args()
67+
68+
try:
69+
70+
if args.debug:
71+
logger.setLevel(logging.DEBUG)
72+
73+
options = {
74+
'baseurl': validate_endpoint(args.endpoint),
75+
'access_key_id': args.access,
76+
'secret_access_key': args.secret,
77+
'bearer': args.bearer
78+
}
79+
80+
validate_directory_and_filename(args.path)
81+
82+
settings.io.direct.api.settings.connector.ssl = not args.no_verify_ssl
83+
settings.io.direct.storage.settings.connector.ssl = not args.no_verify_ssl
84+
85+
asyncio.run(download_from_object_storage(options, args.file_id, args.path))
86+
except ConnectionError:
87+
print(f'Error: Could not establish connection to host: {args.endpoint}:443', file=sys.stderr)
88+
except (AssertionError, TLSError, DirectIOError) as e:
89+
print(e, file=sys.stderr)

cterasdk/direct/client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import cterasdk.settings
33

44
from . import filters
5-
from .credentials import KeyPair, Bearer, create_bearer_token
5+
from .credentials import KeyPair, Bearer
66
from .lib import get_chunks, decrypt_encryption_key, process_chunks
77
from .types import ByteRange
88
from .stream import Streamer
@@ -29,10 +29,9 @@ def __init__(self, baseurl=None, access_key_id=None, secret_access_key=None, bea
2929
authenticator=lambda *_: True)
3030
self._client = AsyncClient(DefaultBuilder(), settings=cterasdk.settings.io.direct.storage.settings, authenticator=lambda *_: True)
3131
self._credentials = Bearer(bearer) if bearer else KeyPair(access_key_id, secret_access_key)
32-
self._bearer = create_bearer_token(self._credentials)
3332

3433
async def _chunks(self, file_id):
35-
metadata = await get_chunks(self._api, self._bearer, file_id)
34+
metadata = await get_chunks(self._api, self._credentials.bearer, file_id)
3635
if metadata.encrypted:
3736
metadata.encryption_key = decrypt_encryption_key(
3837
metadata.file_id,

cterasdk/direct/credentials.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import os
12
import logging
3+
from abc import abstractmethod
24

35

46
logger = logging.getLogger('cterasdk.direct')
@@ -7,38 +9,33 @@
79
class BaseCredentials:
810
"""Base Credentials for CTERA Direct IO"""
911

12+
@property
13+
def bearer(self):
14+
return f'Bearer {self._bearer()}'
15+
16+
@abstractmethod
17+
def _bearer(self):
18+
raise NotImplementedError("Subclass must implemenet the '_bearer' method.")
19+
1020

1121
class KeyPair(BaseCredentials):
1222
"""Access and Secret Key Pair"""
1323

1424
def __init__(self, access_key_id, secret_access_key):
15-
self.access_key_id = access_key_id
16-
self.secret_access_key = secret_access_key
25+
logger.debug('Initializing client using Key Pair.')
26+
self.access_key_id = access_key_id if access_key_id else os.getenv('cterasdk.io.direct.access_key_id')
27+
self.secret_access_key = secret_access_key if secret_access_key else os.getenv('cterasdk.io.direct.secret_access_key')
28+
29+
def _bearer(self):
30+
return self.access_key_id
1731

1832

1933
class Bearer(BaseCredentials):
2034
"""Bearer Token"""
2135

2236
def __init__(self, bearer):
23-
self.bearer = bearer
24-
25-
26-
def create_bearer_token(credentials):
27-
"""
28-
Create Authorization Header.
29-
30-
:param cterasdk.direct.credentials.BaseCredentials credentials: Credentials
31-
:returns: Authorization header as a dictionary.
32-
:rtype: dict
33-
"""
34-
token = None
35-
36-
if isinstance(credentials, Bearer):
37-
logger.debug('Initializing client using Bearer token')
38-
token = f'Bearer {credentials.bearer}'
39-
40-
elif isinstance(credentials, KeyPair):
41-
logger.debug('Initializing client using Key Pair.')
42-
token = f'Bearer {credentials.access_key_id}'
37+
logger.debug('Initializing client using Bearer token.')
38+
self.bearer = bearer if bearer else os.getenv('cterasdk.io.direct.bearer')
4339

44-
return token
40+
def _bearer(self):
41+
return self.bearer

cterasdk/direct/stream.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ async def start(self):
4242
yield fragment
4343
self._offset = fragment.offset + fragment.length
4444
except DirectIOAPIError as error:
45-
raise StreamError(error.filename, self._offset)
45+
raise StreamError(error.filename, self._offset) from error
4646
except BlockError as error:
47-
raise StreamError(error.block.file_id, self._offset)
47+
raise StreamError(error.block.file_id, self._offset) from error
4848
finally:
4949
self.stop()

cterasdk/edge/network.py

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import logging
2+
import ipaddress
23

34
from ..exceptions import CTERAException
45
from ..exceptions.common import TaskException
56
from .enum import Mode, IPProtocol, Traffic
6-
from .types import TCPConnectResult
7-
from ..common import Object, parse_to_ipaddress
7+
from .types import TCPConnectResult, StaticRoute
8+
from ..common import Object
89
from .base_command import BaseCommand
910

1011

@@ -257,55 +258,50 @@ class StaticRoutes(BaseCommand):
257258

258259
def get(self):
259260
"""
260-
Get All Static Routes
261+
Get routes.
261262
"""
262-
return self._edge.api.get('/config/network/static_routes')
263+
return [StaticRoute(r.DestIpMask, r.GwIP) for r in self._edge.api.get('/config/network/static_routes')]
263264

264-
def add(self, source_ip, destination_ip_mask):
265+
def add(self, gateway, network):
265266
"""
266-
Add a Static Route
267+
Add a route.
267268
268-
:param str source_ip: The source IP (192.168.15.55)
269-
:param str destination_ip_mask: The destination IP and CIDR block (10.5.0.1/32)
269+
:param str gateway: Gateway IP address
270+
:param str network: Network (CIDR)
270271
"""
272+
ipaddress.ip_address(gateway)
273+
ipaddress.ip_network(network)
274+
param = Object()
275+
param.GwIP = gateway
276+
param.DestIpMask = network.replace('/', '_')
271277
try:
272-
param = Object()
273-
param.GwIP = str(parse_to_ipaddress(source_ip))
274-
param.DestIpMask = str(parse_to_ipaddress(destination_ip_mask)).replace("/", "_")
275-
res = self._edge.api.add('/config/network/static_routes', param)
276-
logger.info(
277-
"Static route updated. %s", {'Source': param.GwIP, 'Destination': destination_ip_mask})
278-
return res
278+
logger.info('Adding route for network: %s, to: %s', network, param.GwIP)
279+
self._edge.api.add('/config/network/static_routes', param)
280+
logger.info('Route added for network: %s, to: %s', network, param.GwIP)
281+
return StaticRoute(network, gateway)
279282
except CTERAException as error:
280283
logger.error("Static route creation failed.")
281284
raise CTERAException('Static route creation failed') from error
282285

283-
def remove(self, destination_ip_mask):
286+
def delete(self, network):
284287
"""
285-
Remove a Static Route
288+
Delete a route.
286289
287-
:param str destination_ip_mask: The destination IP and CIDR block (10.5.0.1/32)
290+
:param str network: Subnet mask (CIDR)
288291
"""
292+
ipaddress.ip_network(network)
289293
try:
290-
dest_ip_mask = str(parse_to_ipaddress(destination_ip_mask)).replace("/", "_")
291-
response = self._edge.api.delete(f'/config/network/static_routes/{dest_ip_mask}')
292-
logger.info(
293-
"Static route deleted. %s", {'Destination': dest_ip_mask})
294-
return response
294+
logger.info('Deleting route for: %s', network)
295+
self._edge.api.delete(f'/config/network/static_routes/{network.replace("/", "_")}')
296+
logger.info('Route deleted. Subnet: %s', network)
295297
except CTERAException as error:
296298
logger.error("Static route deletion failed.")
297299
raise CTERAException('Static route deletion failed') from error
298300

299301
def clear(self):
300-
"""
301-
Clear All Static routes
302-
"""
303-
try:
304-
self._edge.api.execute('/config/network', 'cleanStaticRoutes')
305-
logger.info('Static routes were deleted successfully')
306-
except CTERAException as error:
307-
logger.error("Failed to clear static routes")
308-
raise CTERAException('Failed to clear static routes') from error
302+
logger.info('Clearing route table.')
303+
self._edge.api.execute('/config/network', 'cleanStaticRoutes')
304+
logger.info('Route table cleared.')
309305

310306

311307
class Hosts(BaseCommand):

cterasdk/edge/types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
'established to the target host over the specified port'
1919

2020

21+
StaticRoute = namedtuple('StaticRoute', ('network', 'gateway'))
22+
StaticRoute.__doc__ = 'Tuple holding the network and gateway of a static route'
23+
StaticRoute.network.__doc__ = 'Network (CIDR)'
24+
StaticRoute.gateway.__doc__ = 'Gateway IP address'
25+
26+
2127
class UserGroupEntry():
2228
"""
2329
User or Group Entry

docs/source/UserGuides/Edge/Configuration.rst

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -928,27 +928,23 @@ Static Routes
928928

929929
.. code-block:: python
930930
931-
# get static routes
932931
edge.network.routes.get()
933932
934933
.. automethod:: cterasdk.edge.network.StaticRoutes.add
935934
:noindex:
936935

937936
.. code-block:: python
938937
939-
# add static route from 10.10.12.1 to 192.168.55.7/32
940938
edge.network.routes.add('10.10.12.1', '192.168.55.7/32')
941939
942-
# add static route from 10.100.102.4 to 172.18.100.0/24
943940
edge.network.routes.add('10.100.102.4', '172.18.100.0/24')
944941
945-
.. automethod:: cterasdk.edge.network.StaticRoutes.remove
942+
.. automethod:: cterasdk.edge.network.StaticRoutes.delete
946943
:noindex:
947944

948945
.. code-block:: python
949946
950-
# remove static route 192.168.55.7/32
951-
edge.network.routes.remove('192.168.55.7/32')
947+
edge.network.routes.delete('192.168.55.7/32')
952948
953949
.. automethod:: cterasdk.edge.network.StaticRoutes.clear
954950
:noindex:

0 commit comments

Comments
 (0)