Skip to content

Commit 8b29e39

Browse files
authored
Saimon/support 78 certificate apis (#233)
1 parent ba31983 commit 8b29e39

File tree

7 files changed

+199
-20
lines changed

7 files changed

+199
-20
lines changed

cterasdk/common/datetime_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def get_expiration_date(expiration):
1818
expiration_date = datetime.date.today() + datetime.timedelta(days=expiration)
1919
elif isinstance(expiration, datetime.date):
2020
expiration_date = expiration
21-
return expiration_date
21+
return expiration_date # pylint: disable=possibly-used-before-assignment
2222

2323

2424
def from_iso_format(time):

cterasdk/edge/ssl.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
from ..common import Object
66

77

8+
def initialize(edge):
9+
"""
10+
Conditional intialization of the Edge Filer SSL Module.
11+
"""
12+
if edge.session().version > '7.8':
13+
return SSLv78(edge)
14+
return SSLv1(edge)
15+
16+
817
class SSL(BaseCommand):
918
""" Edge Filer SSL APIs """
1019

@@ -38,6 +47,10 @@ def _get_force_https(self):
3847
def _set_force_https(self, force):
3948
self._edge.api.put('/config/fileservices/webdav/forceHttps', force)
4049

50+
51+
class SSLv1(SSL):
52+
""" Edge Filer SSLv1 APIs """
53+
4154
def remove_storage_ca(self):
4255
"""
4356
Remove object storage trusted CA certificate
@@ -81,3 +94,94 @@ def import_certificate(self, private_key, *certificates):
8194
response = self._edge.api.put('/config/certificate', f"\n{server_certificate}")
8295
logging.getLogger('cterasdk.edge').info("Uploaded SSL certificate.")
8396
return response
97+
98+
99+
class SSLv78(SSL):
100+
""" Edge Filer v7.8 SSL Certificate APIs """
101+
102+
def __init__(self, edge):
103+
super().__init__(edge)
104+
self.server = ServerCertificate(edge)
105+
self.ca = TrustedCAs(edge)
106+
107+
108+
class ServerCertificate(BaseCommand):
109+
""" Edge Filer v7.8 Server Certificate APIs """
110+
111+
def get(self):
112+
"""
113+
Get Server Cerificate.
114+
"""
115+
return self._edge.api.get('/proc/certificates/serverCertificate')
116+
117+
def regenerate(self):
118+
"""
119+
Generate a Self Signed Certificate.
120+
"""
121+
logging.getLogger('cterasdk.edge').info("Generating a Self Signed Certificate.")
122+
response = self._edge.api.execute('/config/certificates', 'createSelfSign')
123+
logging.getLogger('cterasdk.edge').info("Generated a Self Signed Certificate.")
124+
return response
125+
126+
def import_certificate(self, private_key, *certificates):
127+
"""
128+
Import the Edge Filer's server SSL certificate
129+
130+
:param str private_key: The PEM-encoded private key, or a path to the PEM-encoded private key file
131+
:param list[str] certificates: The PEM-encoded certificates, or a list of paths to the PEM-encoded certificates
132+
"""
133+
key_object = PrivateKey.load_private_key(private_key)
134+
certificates = [X509Certificate.load_certificate(certificate) for certificate in certificates]
135+
certificate_chain = [certificate.pem_data.decode('utf-8') for certificate in create_certificate_chain(*certificates)]
136+
server_certificate = ''.join([key_object.pem_data.decode('utf-8')] + certificate_chain)
137+
logging.getLogger('cterasdk.edge').info("Uploading SSL certificate.")
138+
response = self._edge.api.put('/config/certificates/serverCertificate', f"\n{server_certificate}")
139+
logging.getLogger('cterasdk.edge').info("Uploaded SSL certificate.")
140+
return response
141+
142+
143+
class TrustedCAs(BaseCommand):
144+
""" Edge Filer v7.8 Trusted CAs APIs """
145+
146+
def all(self):
147+
"""
148+
List Trusted CAs.
149+
"""
150+
return self._edge.api.get('/proc/certificates/trustedCACertificates')
151+
152+
def add(self, ca):
153+
"""
154+
Add Trusted CA.
155+
156+
:param str certificate: The PEM-encoded certificate or a path to the PEM-encoded server certificate file
157+
"""
158+
certificate = X509Certificate.load_certificate(ca).pem_data.decode('utf-8')
159+
return self._edge.api.execute('/config/certificates', 'addTrustedCACert', certificate)
160+
161+
def remove(self, ca):
162+
"""
163+
Remove Trusted CA.
164+
165+
:param object ca: CA fingerprint as `str`, or a trusted CA object.
166+
"""
167+
fingerprint = None
168+
if isinstance(ca, Object) and hasattr(ca, 'fingerprint'):
169+
fingerprint = ca.fingerprint
170+
else:
171+
fingerprint = ca
172+
173+
if fingerprint:
174+
logging.getLogger('cterasdk.edge').info("Removing Trusted CA. %s", {'fingerprint': fingerprint})
175+
response = self._edge.api.delete(f'/config/certificates/trustedCACertificates/{fingerprint}')
176+
logging.getLogger('cterasdk.edge').info("Removed Trusted CA. %s", {'fingerprint': fingerprint})
177+
return response
178+
179+
raise ValueError('Could not identify CA fingerprint.')
180+
181+
def clear(self):
182+
"""
183+
Remove all Trusted CAs.
184+
"""
185+
logging.getLogger('cterasdk.edge').info("Removing all Trusted CAs.")
186+
for ca in self.all():
187+
self.remove(ca)

cterasdk/objects/edge.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ def __init__(self, host=None, port=None, https=True, Portal=None, *, base=None):
149149
self.users = users.Users(self)
150150
self.volumes = volumes.Volumes(self)
151151

152+
def _after_login(self):
153+
self.ssl = ssl.initialize(self)
154+
152155
@property
153156
def migrate(self):
154157
return self.clients.migrate

cterasdk/objects/services.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def __init__(self, host, port, https, base):
4444
def clients(self):
4545
return self._ctera_clients
4646

47+
def _before_login(self):
48+
"""Override to implement logic before login"""
49+
50+
def _after_login(self):
51+
"""Override to implement logic after login"""
52+
4753
@property
4854
@abstractmethod
4955
def _login_object(self):
@@ -80,8 +86,10 @@ def login(self, username, password):
8086
:param str username: Username
8187
:param str password: Password
8288
"""
89+
self._before_login()
8390
self._login_object.login(username, password)
8491
self._ctera_session.start_local_session(self)
92+
self._after_login()
8593

8694
def logout(self):
8795
""" Log out """

docs/source/UserGuides/Edge/Configuration.rst

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,41 +1039,75 @@ Reset to Defaults
10391039
SSL Certificate
10401040
===============
10411041

1042-
.. automethod:: cterasdk.edge.ssl.SSL.disable_http
1042+
SSL management commands supported starting Edge Filer v7.8 or higher:
1043+
1044+
.. automethod:: cterasdk.edge.ssl.ServerCertificate.get
10431045
:noindex:
10441046

10451047
.. code-block:: python
10461048
1047-
edge.ssl.disable_http()
1049+
server_certificate = edge.ssl.server.get()
1050+
print(server_certificate.fingerprint)
10481051
1049-
.. automethod:: cterasdk.edge.ssl.SSL.enable_http
1052+
.. automethod:: cterasdk.edge.ssl.ServerCertificate.regenerate
10501053
:noindex:
10511054

10521055
.. code-block:: python
10531056
1054-
edge.ssl.enable_http()
1057+
edge.ssl.server.regenerate() # generate a self-signed server certificate
10551058
1056-
.. automethod:: cterasdk.edge.ssl.SSL.is_http_disabled
1059+
.. automethod:: cterasdk.edge.ssl.ServerCertificate.import_certificate
10571060
:noindex:
10581061

10591062
.. code-block:: python
10601063
1061-
edge.ssl.is_http_disabled()
1064+
edge.ssl.server.import_certificate(
1065+
r'C:/users/username/certificate/private.key',
1066+
r'C:/users/username/certificate/certificate.crt',
1067+
r'C:/users/username/certificate/intermediate1.crt',
1068+
r'C:/users/username/certificate/intermediate2.crt',
1069+
r'C:/users/username/certificate/root.crt'
1070+
)
10621071
1063-
.. automethod:: cterasdk.edge.ssl.SSL.is_http_enabled
1072+
.. automethod:: cterasdk.edge.ssl.TrustedCAs.all
10641073
:noindex:
10651074

10661075
.. code-block:: python
10671076
1068-
edge.ssl.is_http_enabled()
1077+
for ca_certificate in edge.ssl.ca.all():
1078+
print(ca_certificate.fingerprint)
10691079
1070-
.. automethod:: cterasdk.edge.ssl.SSL.get_storage_ca
1080+
.. automethod:: cterasdk.edge.ssl.TrustedCAs.add
10711081
:noindex:
10721082

1073-
.. automethod:: cterasdk.edge.ssl.SSL.remove_storage_ca
1083+
.. code-block:: python
1084+
1085+
edge.ssl.ca.add(r'C:/users/username/certificate/ca.crt') # add Trusted CA certificate
1086+
1087+
.. automethod:: cterasdk.edge.ssl.TrustedCAs.remove
10741088
:noindex:
10751089

1076-
.. automethod:: cterasdk.edge.ssl.SSL.import_certificate
1090+
.. code-block:: python
1091+
1092+
certificate_fingerprint = '04:a0:56:a9:87:64:bb:dc:96:bf:6d:b0:49:fa:80:81:ed:06:8a:1e'
1093+
edge.ssl.ca.remove(certificate_fingerprint)
1094+
1095+
.. automethod:: cterasdk.edge.ssl.TrustedCAs.clear
1096+
:noindex:
1097+
1098+
.. code-block:: python
1099+
1100+
edge.ssl.ca.clear()
1101+
1102+
SSL management commands supported up to Edge Filer v7.6:
1103+
1104+
.. automethod:: cterasdk.edge.ssl.SSLv1.get_storage_ca
1105+
:noindex:
1106+
1107+
.. automethod:: cterasdk.edge.ssl.SSLv1.remove_storage_ca
1108+
:noindex:
1109+
1110+
.. automethod:: cterasdk.edge.ssl.SSLv1.import_certificate
10771111
:noindex:
10781112

10791113
.. code-block:: python
@@ -1093,6 +1127,36 @@ SSL Certificate
10931127
10941128
.. danger: Exercise caution. Test thoroughly prior to implementing in production. Ensure the integrity of the PEM encoded private key and certificates. Supplying an invalid private key or certificate will disable administrative access to the filer and would require CTERA Support to re-enable it.
10951129
1130+
SSL management commands supported in all Edge Filer versions:
1131+
1132+
.. automethod:: cterasdk.edge.ssl.SSL.disable_http
1133+
:noindex:
1134+
1135+
.. code-block:: python
1136+
1137+
edge.ssl.disable_http()
1138+
1139+
.. automethod:: cterasdk.edge.ssl.SSL.enable_http
1140+
:noindex:
1141+
1142+
.. code-block:: python
1143+
1144+
edge.ssl.enable_http()
1145+
1146+
.. automethod:: cterasdk.edge.ssl.SSL.is_http_disabled
1147+
:noindex:
1148+
1149+
.. code-block:: python
1150+
1151+
edge.ssl.is_http_disabled()
1152+
1153+
.. automethod:: cterasdk.edge.ssl.SSL.is_http_enabled
1154+
:noindex:
1155+
1156+
.. code-block:: python
1157+
1158+
edge.ssl.is_http_enabled()
1159+
10961160
Power Management
10971161
================
10981162

tests/ut/test_core_users.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def _test_add_user_with_password_change(self, password_change):
6969
expiration_date = datetime.date.today() + datetime.timedelta(days=password_change)
7070
elif isinstance(password_change, datetime.date):
7171
expiration_date = password_change
72-
expected_requirePasswordChangeOn = expiration_date.strftime('%Y-%m-%d')
72+
expected_requirePasswordChangeOn = expiration_date.strftime('%Y-%m-%d') # pylint: disable=possibly-used-before-assignment
7373
expected_param = self._get_user_object(
7474
name=self._username,
7575
email=self._email,
@@ -79,7 +79,7 @@ def _test_add_user_with_password_change(self, password_change):
7979
role=self._role,
8080
company=None,
8181
comment=None,
82-
requirePasswordChangeOn=expected_requirePasswordChangeOn
82+
requirePasswordChangeOn=expected_requirePasswordChangeOn # pylint: disable=possibly-used-before-assignment
8383
)
8484
actual_param = self._global_admin.api.add.call_args[0][1]
8585
self._assert_equal_objects(actual_param, expected_param)
@@ -247,7 +247,7 @@ def _test_add_admin_user_with_password_change(self, password_change):
247247
expiration_date = datetime.date.today() + datetime.timedelta(days=password_change)
248248
elif isinstance(password_change, datetime.date):
249249
expiration_date = password_change
250-
expected_requirePasswordChangeOn = expiration_date.strftime('%Y-%m-%d')
250+
expected_requirePasswordChangeOn = expiration_date.strftime('%Y-%m-%d') # pylint: disable=possibly-used-before-assignment
251251
expected_param = self._get_admin_object(
252252
name=self._username,
253253
email=self._email,
@@ -257,7 +257,7 @@ def _test_add_admin_user_with_password_change(self, password_change):
257257
role=self._role,
258258
company=None,
259259
comment=None,
260-
requirePasswordChangeOn=expected_requirePasswordChangeOn
260+
requirePasswordChangeOn=expected_requirePasswordChangeOn # pylint: disable=possibly-used-before-assignment
261261
)
262262
actual_param = self._global_admin.api.add.call_args[0][1]
263263
self._assert_equal_objects(actual_param, expected_param)

tests/ut/test_edge_ssl.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_import_certificate(self):
6262
mock_load_private_key.return_value = TestEdgeSSL._get_secret(self._private_key_contents)
6363
mock_load_certificate = self.patch_call("cterasdk.lib.crypto.X509Certificate.load_certificate")
6464
mock_load_certificate.return_value = TestEdgeSSL._get_secret(self._certificate_contents)
65-
ret = ssl.SSL(self._filer).import_certificate(self._key_filepath, self._domain_cert, self._intermediate_cert, self._root_cert)
65+
ret = ssl.SSLv1(self._filer).import_certificate(self._key_filepath, self._domain_cert, self._intermediate_cert, self._root_cert)
6666
mock_load_private_key.assert_called_once_with(self._key_filepath)
6767
mock_load_certificate.assert_has_calls(
6868
[
@@ -83,7 +83,7 @@ def test_import_certificate(self):
8383
def test_get_storage_ca(self):
8484
get_response = 'Success'
8585
self._init_filer(get_response=get_response)
86-
ret = ssl.SSL(self._filer).get_storage_ca()
86+
ret = ssl.SSLv1(self._filer).get_storage_ca()
8787
self._filer.api.get.assert_called_once_with('/status/extStorageTrustedCA')
8888
self.assertEqual(ret, get_response)
8989

@@ -92,7 +92,7 @@ def test_import_storage_ca(self):
9292
self._init_filer(put_response=put_response)
9393
mock_load_certificate = self.patch_call("cterasdk.lib.crypto.X509Certificate.load_certificate")
9494
mock_load_certificate.return_value = TestEdgeSSL._get_secret(self._certificate_contents)
95-
ret = ssl.SSL(self._filer).import_storage_ca(self._domain_cert)
95+
ret = ssl.SSLv1(self._filer).import_storage_ca(self._domain_cert)
9696
expected_param = Object()
9797
expected_param._classname = 'ExtTrustedCA' # pylint: disable=protected-access
9898
expected_param.certificate = self._certificate_contents
@@ -105,7 +105,7 @@ def test_import_storage_ca(self):
105105
def test_remove_storage_ca(self):
106106
put_response = 'Success'
107107
self._init_filer(put_response=put_response)
108-
ssl.SSL(self._filer).remove_storage_ca()
108+
ssl.SSLv1(self._filer).remove_storage_ca()
109109
self._filer.api.put.assert_called_once_with('/config/extStorageTrustedCA', None)
110110

111111
@staticmethod

0 commit comments

Comments
 (0)