pyrelukko 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyrelukko might be problematic. Click here for more details.
- pyrelukko-0.2.0/.pylintrc.toml +9 -0
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/PKG-INFO +1 -1
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/pyproject.toml +8 -0
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/src/pyrelukko/pyrelukko.py +46 -21
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/src/pyrelukko/version.py +1 -1
- pyrelukko-0.2.0/tests/cert/47615cfb.0 +1 -0
- pyrelukko-0.2.0/tests/cert/README.md +8 -0
- pyrelukko-0.2.0/tests/cert/relukko.crt +21 -0
- pyrelukko-0.2.0/tests/cert/relukko.csr +16 -0
- pyrelukko-0.2.0/tests/cert/relukko.der +0 -0
- pyrelukko-0.2.0/tests/cert/relukko.key +28 -0
- pyrelukko-0.2.0/tests/cert/rootCA.crt +21 -0
- pyrelukko-0.2.0/tests/cert/rootCA.der +0 -0
- pyrelukko-0.2.0/tests/cert/rootCA.key +28 -0
- pyrelukko-0.2.0/tests/cert/rootCA.srl +1 -0
- pyrelukko-0.2.0/tests/conftest.py +129 -0
- pyrelukko-0.2.0/tests/initdb.d/20240930160154_init.up.sql +19 -0
- pyrelukko-0.2.0/tests/test_relukko.py +470 -0
- pyrelukko-0.1.0/.pylintrc.toml +0 -4
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/LICENSE +0 -0
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/README.md +0 -0
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/src/pyrelukko/__init__.py +0 -0
- {pyrelukko-0.1.0 → pyrelukko-0.2.0}/src/pyrelukko/retry.py +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
[tool.pylint.main]
|
|
2
|
+
# Files or directories to be skipped. They should be base names, not paths.
|
|
3
|
+
ignore = ["LICENSE", "pyproject.toml", "version.py",
|
|
4
|
+
"README.md", "demo.py", "conftest.py"]
|
|
5
|
+
|
|
6
|
+
# Files or directories matching the regular expression patterns are skipped. The
|
|
7
|
+
# regex matches against base names, not paths. The default value ignores Emacs
|
|
8
|
+
# file locks
|
|
9
|
+
ignore-patterns = ["^\\.#", "^test_"]
|
|
@@ -29,3 +29,11 @@ Issues = "https://gitlab.com/relukko/pyrelukko/-/issues"
|
|
|
29
29
|
[build-system]
|
|
30
30
|
requires = ["flit_core >=3.2,<4", "semantic-version >= 2.10"]
|
|
31
31
|
build-backend = "flit_core.buildapi"
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
testpaths = [
|
|
35
|
+
"tests",
|
|
36
|
+
]
|
|
37
|
+
pythonpath = [
|
|
38
|
+
"src",
|
|
39
|
+
]
|
|
@@ -2,22 +2,20 @@
|
|
|
2
2
|
TBD
|
|
3
3
|
"""
|
|
4
4
|
import asyncio
|
|
5
|
+
import json
|
|
5
6
|
import logging
|
|
6
7
|
import os
|
|
7
|
-
import json
|
|
8
|
-
import threading
|
|
9
8
|
import ssl
|
|
9
|
+
import threading
|
|
10
10
|
import time
|
|
11
|
-
|
|
12
11
|
from datetime import datetime
|
|
13
|
-
from typing import Dict, List, Union
|
|
14
12
|
from pathlib import Path
|
|
15
|
-
from
|
|
16
|
-
from urllib3.util.retry import Retry
|
|
13
|
+
from typing import Dict, List, Union
|
|
17
14
|
|
|
18
15
|
import requests
|
|
16
|
+
from urllib3.util import Url, parse_url
|
|
17
|
+
from urllib3.util.retry import Retry
|
|
19
18
|
from websockets import ConnectionClosed as WsConnectionClosed
|
|
20
|
-
|
|
21
19
|
from websockets.asyncio.client import connect as ws_connect
|
|
22
20
|
|
|
23
21
|
from .retry import retry
|
|
@@ -30,6 +28,14 @@ SSL_KWARGS = [
|
|
|
30
28
|
'options',
|
|
31
29
|
]
|
|
32
30
|
|
|
31
|
+
RETRY_KWARGS = [
|
|
32
|
+
'tries',
|
|
33
|
+
'delay',
|
|
34
|
+
'backoff',
|
|
35
|
+
'max_delay',
|
|
36
|
+
'exceptions'
|
|
37
|
+
]
|
|
38
|
+
|
|
33
39
|
logger = logging.getLogger(__name__)
|
|
34
40
|
|
|
35
41
|
|
|
@@ -50,12 +56,21 @@ class RelukkoClient:
|
|
|
50
56
|
"""
|
|
51
57
|
self.session = requests.Session()
|
|
52
58
|
self.api_key = api_key
|
|
59
|
+
self.tries=4
|
|
60
|
+
self.delay=5
|
|
61
|
+
self.backoff=2.0
|
|
62
|
+
self.max_delay=None
|
|
63
|
+
self.exceptions = (
|
|
64
|
+
requests.ConnectionError,
|
|
65
|
+
RelukkoDoRetry,
|
|
66
|
+
)
|
|
53
67
|
self._setup_session(api_key, **kwargs)
|
|
54
68
|
self._setup_http_adapters_retry(**kwargs)
|
|
69
|
+
self._setup_pyrelukko_retry(**kwargs)
|
|
55
70
|
|
|
56
71
|
self.base_url = self._setup_base_url(base_url)
|
|
57
72
|
self.ws_url = self._setup_ws_url(str(self.base_url))
|
|
58
|
-
self.ssl_ctx = None
|
|
73
|
+
self.ssl_ctx: ssl.SSLContext = None
|
|
59
74
|
self._setup_ssl_ctx(**kwargs)
|
|
60
75
|
|
|
61
76
|
# event for websocket thread to signal it got a message
|
|
@@ -75,6 +90,15 @@ class RelukkoClient:
|
|
|
75
90
|
self.base_url = self._setup_base_url(base_url or self.base_url)
|
|
76
91
|
self.ws_url = self._setup_ws_url(str(self.base_url))
|
|
77
92
|
self._setup_ssl_ctx(**kwargs)
|
|
93
|
+
self._setup_pyrelukko_retry(**kwargs)
|
|
94
|
+
|
|
95
|
+
def _setup_pyrelukko_retry(self, **kwargs):
|
|
96
|
+
for kwarg in RETRY_KWARGS:
|
|
97
|
+
setattr(
|
|
98
|
+
self,
|
|
99
|
+
kwarg,
|
|
100
|
+
kwargs.get(kwarg, getattr(self, kwarg))
|
|
101
|
+
)
|
|
78
102
|
|
|
79
103
|
def _setup_http_adapters_retry(self, **kwargs):
|
|
80
104
|
for _, http_adapter in self.session.adapters.items():
|
|
@@ -85,7 +109,7 @@ class RelukkoClient:
|
|
|
85
109
|
http_adapter.max_retries = http_retry
|
|
86
110
|
|
|
87
111
|
def _setup_session(self, api_key: str, **kwargs):
|
|
88
|
-
self.session.headers
|
|
112
|
+
self.session.headers['X-api-Key'] = api_key
|
|
89
113
|
for key, value in kwargs.items():
|
|
90
114
|
if hasattr(self.session, key):
|
|
91
115
|
setattr(self.session, key, value)
|
|
@@ -116,7 +140,7 @@ class RelukkoClient:
|
|
|
116
140
|
|
|
117
141
|
# values from kwargs take precedence env vars
|
|
118
142
|
ca_file = kwargs.get('cafile', ca_bundle_file)
|
|
119
|
-
ca_path = kwargs.get('capath', ca_bundle_path)
|
|
143
|
+
ca_path = kwargs.get('capath', f"{ca_bundle_path}/")
|
|
120
144
|
ca_data = kwargs.get('cadata')
|
|
121
145
|
|
|
122
146
|
if ca_file or ca_path or ca_data:
|
|
@@ -196,7 +220,7 @@ class RelukkoClient:
|
|
|
196
220
|
|
|
197
221
|
def _check_response(self, response: requests.Response):
|
|
198
222
|
match response.status_code:
|
|
199
|
-
case 200 | 201 | 404:
|
|
223
|
+
case 200 | 201 | 404 | 422:
|
|
200
224
|
return response.json()
|
|
201
225
|
case 400 | 403:
|
|
202
226
|
err = response.json()
|
|
@@ -207,10 +231,12 @@ class RelukkoClient:
|
|
|
207
231
|
logger.info(err.get('status'), err.get('message'))
|
|
208
232
|
return None
|
|
209
233
|
case 500 | 502 | 503 | 504:
|
|
210
|
-
logger.warning(
|
|
234
|
+
logger.warning("[%d](%s) %s",
|
|
235
|
+
response.status_code, response.reason, response.text)
|
|
211
236
|
raise RelukkoDoRetry()
|
|
212
237
|
case _:
|
|
213
|
-
logger.warning(
|
|
238
|
+
logger.warning("[%d](%s) %s",
|
|
239
|
+
response.status_code, response.reason, response.text)
|
|
214
240
|
raise RuntimeError()
|
|
215
241
|
|
|
216
242
|
def _make_request(
|
|
@@ -219,12 +245,10 @@ class RelukkoClient:
|
|
|
219
245
|
method: str,
|
|
220
246
|
payload: Dict=None) -> requests.Response:
|
|
221
247
|
|
|
222
|
-
excpetions = (
|
|
223
|
-
requests.ConnectionError,
|
|
224
|
-
RelukkoDoRetry,
|
|
225
|
-
)
|
|
226
248
|
|
|
227
|
-
@retry(logger, exceptions=
|
|
249
|
+
@retry(logger, exceptions=self.exceptions, tries=self.tries,
|
|
250
|
+
delay=self.delay, backoff=self.backoff,
|
|
251
|
+
max_delay=self.max_delay)
|
|
228
252
|
def _do_request():
|
|
229
253
|
response = self.session.request(
|
|
230
254
|
method=method,
|
|
@@ -274,13 +298,14 @@ class RelukkoClient:
|
|
|
274
298
|
url = f"{self.base_url}/v1/locks/"
|
|
275
299
|
return self._make_request(url, "GET")
|
|
276
300
|
|
|
277
|
-
def update_relukko(
|
|
301
|
+
def update_relukko(
|
|
302
|
+
self, lock_id: str, creator: str=None, expires_at: datetime=None):
|
|
278
303
|
"""
|
|
279
304
|
TBD
|
|
280
305
|
"""
|
|
281
306
|
if isinstance(expires_at, datetime):
|
|
282
307
|
expires_at = expires_at.isoformat()
|
|
283
|
-
|
|
308
|
+
elif expires_at is not None:
|
|
284
309
|
raise ValueError("has to be datetime!")
|
|
285
310
|
|
|
286
311
|
payload = {
|
|
@@ -288,7 +313,7 @@ class RelukkoClient:
|
|
|
288
313
|
"expires_at": expires_at,
|
|
289
314
|
}
|
|
290
315
|
url = f"{self.base_url}/v1/locks/{lock_id}"
|
|
291
|
-
return self._make_request(url, "
|
|
316
|
+
return self._make_request(url, "PUT", payload)
|
|
292
317
|
|
|
293
318
|
def delete_relukko(self, lock_id: str):
|
|
294
319
|
"""
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# pylint: disable=all
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rootCA.crt
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# How To
|
|
2
|
+
```
|
|
3
|
+
openssl req -x509 -sha256 -days 7300 -noenc -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt
|
|
4
|
+
ln -s rootCA.crt "$(openssl x509 -hash -noout -in rootCA.crt)"
|
|
5
|
+
openssl req -newkey rsa:2048 -noenc -keyout relukko.key -out relukko.csr
|
|
6
|
+
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in relukko.csr -out relukko.crt -days 7299 -CAcreateserial
|
|
7
|
+
openssl x509 -inform PEM -in rootCA.crt -outform DER -out rootCA.der
|
|
8
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDeDCCAmCgAwIBAgIUIG2mO1IxfM3wqiLLFCO+6ubGdHswDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
|
|
4
|
+
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHUmVsdWtrbzAeFw0yNDEx
|
|
5
|
+
MTMxNTQxNTdaFw00NDExMDcxNTQxNTdaMFQxCzAJBgNVBAYTAlhYMRUwEwYDVQQH
|
|
6
|
+
DAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEDAO
|
|
7
|
+
BgNVBAMMB3JlbHVra28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr
|
|
8
|
+
89++zNQs+MG5opoCTWq/QxAJsr9Uul598XoQFXn1N/d/JOeSosy8BNvb1RXsr5sp
|
|
9
|
+
sNJbJEun6OPFtUoVczUtivS99XNchWPjEQ18w22aiLcwl/uOrdfxWj04yEfKmejN
|
|
10
|
+
ZXFrShhAWRGfc3fi2hD0Y5mtbS7T5iqaYzm4U5UDVFizVPnSgPFXMAiat/VQGvBF
|
|
11
|
+
cv5IpBjXOJGkLZTzmFlW61T6m+MJGXF59CLC6vL6QJCqQpSUWG9UM00R8L2pQ1b8
|
|
12
|
+
HLKYUuJ+vp4WVYwYyz769BNG4OG3IkdqIHKDFWBXFVmKaUkX9aAul4BOitKy4XFc
|
|
13
|
+
8eu653lbkVnRHb01RaP7AgMBAAGjQjBAMB0GA1UdDgQWBBSEFybZBM4O7khSofZl
|
|
14
|
+
0oAT+CSXEjAfBgNVHSMEGDAWgBRAo64Da82emF0q53wCdj4wbFTmMDANBgkqhkiG
|
|
15
|
+
9w0BAQsFAAOCAQEAs+zaKt8VqLoVT0OT24Oxyzc+/A0h4BYq3kYD11BaUJ6oKqQu
|
|
16
|
+
57FRKi0ZWxNSNCbzrwi6W+qNgIXtvSHWk4+BKxzqDVG6LCIuNn4bbvlQMGuTWctY
|
|
17
|
+
ndArPxdKvN9AXeCvam+5XKcA5OJY/naoho7fSoGh2Zgkq9HS7DlKxfBW2GEY0mp8
|
|
18
|
+
jeuxvBvh4xw/YuESVFbKedglaoNFf+aP44zL47Wn9QJcLqByGpTV472j238ObUFi
|
|
19
|
+
k47o+v4Qt7nkJDOcx7ic//uLTcb/1rv1SppYpKr3YJ5fLspzMMPpTQbkt2H8hLiZ
|
|
20
|
+
+1jj5GIoDgLi+/uxBgYQs78jEOIKkWO6EjwGUw==
|
|
21
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE REQUEST-----
|
|
2
|
+
MIICmTCCAYECAQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0
|
|
3
|
+
eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHcmVsdWtr
|
|
4
|
+
bzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKvz377M1Cz4wbmimgJN
|
|
5
|
+
ar9DEAmyv1S6Xn3xehAVefU3938k55KizLwE29vVFeyvmymw0lskS6fo48W1ShVz
|
|
6
|
+
NS2K9L31c1yFY+MRDXzDbZqItzCX+46t1/FaPTjIR8qZ6M1lcWtKGEBZEZ9zd+La
|
|
7
|
+
EPRjma1tLtPmKppjObhTlQNUWLNU+dKA8VcwCJq39VAa8EVy/kikGNc4kaQtlPOY
|
|
8
|
+
WVbrVPqb4wkZcXn0IsLq8vpAkKpClJRYb1QzTRHwvalDVvwcsphS4n6+nhZVjBjL
|
|
9
|
+
Pvr0E0bg4bciR2ogcoMVYFcVWYppSRf1oC6XgE6K0rLhcVzx67rneVuRWdEdvTVF
|
|
10
|
+
o/sCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCHkmw8bBBI9pOrlCrJ2u3sGdlx
|
|
11
|
+
3ZbW/+kGdsQ8Odb/pIHkLLKpu5yNLEey/U4EZyWSNewYDUKfS2FIyMN2c38QAex6
|
|
12
|
+
1Y9qPK74RzUB68ki7XKtp+GAGqVb0axxRe0IuBwc9d5jl6zjltcpdutAIIgXcJgg
|
|
13
|
+
8QPnfpz7Skhjjm/duTVp0rurWsPztQdDpkrcY3c49vuTLA5uKwfa//cPAOmVGwqq
|
|
14
|
+
TMICj+0M2S+bgg9Ksph1IsDxxuIm1aa2EFnF5XRbJn9aOSdyfBkKoZz9ttgu1hP4
|
|
15
|
+
+hPe8S3ekUyJiZAglSI1qJOkpyw14EDIhwJvE9FG5eN/mU8wS1/GU+vhZWKN
|
|
16
|
+
-----END CERTIFICATE REQUEST-----
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
|
2
|
+
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCr89++zNQs+MG5
|
|
3
|
+
opoCTWq/QxAJsr9Uul598XoQFXn1N/d/JOeSosy8BNvb1RXsr5spsNJbJEun6OPF
|
|
4
|
+
tUoVczUtivS99XNchWPjEQ18w22aiLcwl/uOrdfxWj04yEfKmejNZXFrShhAWRGf
|
|
5
|
+
c3fi2hD0Y5mtbS7T5iqaYzm4U5UDVFizVPnSgPFXMAiat/VQGvBFcv5IpBjXOJGk
|
|
6
|
+
LZTzmFlW61T6m+MJGXF59CLC6vL6QJCqQpSUWG9UM00R8L2pQ1b8HLKYUuJ+vp4W
|
|
7
|
+
VYwYyz769BNG4OG3IkdqIHKDFWBXFVmKaUkX9aAul4BOitKy4XFc8eu653lbkVnR
|
|
8
|
+
Hb01RaP7AgMBAAECggEAF22KShjmkwavf7HfnDmyYMMWhSO//XTJBA1/DSrylg5d
|
|
9
|
+
NQsGVsxslZ9vLyP4sT9PJjPWbUmO2fPuo1TVy6y0jZ3K8Qi8rNnrEphpBzB/Qe38
|
|
10
|
+
+5dxfu6WUebIGZucFca42SdITTm9VSMYNhU0O98XLp0AEV4jYlvlPmoPhrAzSM50
|
|
11
|
+
lEIuriNhqHuqZhXN0jPYI1jZDS5JVi2PlbgyO1125BBMsCMLfhW+wcme/Y/VAySc
|
|
12
|
+
pNkvK3aHeWJUXEeVKuNfn5878PqeD6ZPjl9Hyq3lj6yOauIO/PCdIwm+2qjzrYsy
|
|
13
|
+
IBhjFFEoMGofgz+KV7olZu9by38ZzhW7nZGGe+zbgQKBgQDmFyzzxz3/BF7FKTXL
|
|
14
|
+
v6uVNkGpMGH8EQE1H+YJXTiSaqf/3ZrsXyBsbA6YBanKBXcY/F0PhQ7e/rjtqaGU
|
|
15
|
+
OtekNTHSgosdbJgVX88EjfIa4/rbZPUsX6qRJBuDVOCcCZC1PyFBT2ehKYlIPHS0
|
|
16
|
+
klbXo57pYzgDnzrYPr6UWbCZewKBgQC/UMGNvqJXszYZe3hjd9Ek9oSJMThfpfXn
|
|
17
|
+
opNhCNHKZ8+xqry602TqIwyStYc7elB57R547ZR4kttT8iM1E7ZOoLRPP0r/FjOS
|
|
18
|
+
scpCAk5ouVG9RZaUvTIbvzdEifEOt1Vf0RgIG3Y4DBqtotNoymqyT5wzuxyJQm1E
|
|
19
|
+
683UVIbXgQKBgQCLPJ0UTI7kwtVCxIRsbum7WuDzLHcvHW84obwIEKSKXgaaHJWC
|
|
20
|
+
0rIBSoauUkcEHLiMozMBkEiGg2iPUaaY197k3NfwhtT+kleaH6dcHzXSNgH5QCfp
|
|
21
|
+
mV7ThCEuIW/mnRc3xyMtrYqNiWAtGYCaQTBSQA6LN2KPNo1ajOWxSnFG/wKBgQC2
|
|
22
|
+
9rBksq/nV4ihjidwWSI3S1stKVlUgA9QW3a/EgQwol9K9pJPyeN019gqZljSVQOp
|
|
23
|
+
10+RLwUS2r/O5H8vP47WW3KVZ1593emsnUNlJXd/R9wYOvjrfpTxXEmqzpEvFb4c
|
|
24
|
+
SIfHGRxSNaE99b5hNVQc+23TO1rrGhAOHcVXDw92AQKBgQDCMEcIcwHx+ahVZbEb
|
|
25
|
+
bAyWLwYxQFRStmYKbYBtEMFW39atRmc0wdAG0my/0cUyHUIfWbHvP5oTfyAthV9G
|
|
26
|
+
8G0qBk5fPhh6hfNRFMb2j1AqnZ3CadoBZq2HelTTUqt4UgU9Y4iDco+4ew34Uw76
|
|
27
|
+
2ki1bKXiJKkJYlk14AMrzckDUQ==
|
|
28
|
+
-----END PRIVATE KEY-----
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDiTCCAnGgAwIBAgIUeLTh5JgTZlG0dbwtCICijkg1MWwwDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwVDELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
|
|
4
|
+
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDEQMA4GA1UEAwwHUmVsdWtrbzAeFw0yNDEx
|
|
5
|
+
MTMxNTM3MDBaFw00NDExMDgxNTM3MDBaMFQxCzAJBgNVBAYTAlhYMRUwEwYDVQQH
|
|
6
|
+
DAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEDAO
|
|
7
|
+
BgNVBAMMB1JlbHVra28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD4
|
|
8
|
+
PWQPjCCXOvZYUDpYhZHR4Nw0ErkZEZWtPMyoBnyGkYwqK1SrHCcIYQL8C22dN1rn
|
|
9
|
+
VdFvCVH13fQHz1SJ/v3jkS+9IiOUDenXpZP/G0pprTbLGh33aaQU53EqvO1T2CAA
|
|
10
|
+
zIAVdme0cihOB+KL2A5bDzP9fu+c0ddn+N++ZgcqBHMYmZKIGJv5QZ+i+/1xrGqQ
|
|
11
|
+
/znwV7RuBGms8fxA3x4Y4ELUzIHMpbXbQn95qC9u7ee4odQ5KjR1M0oocPKOYBGa
|
|
12
|
+
/Dqnhd3Wx0lHR3No8HgC4TBfxruhs+NZtRyInOTFr99TWbBx+zrDvxecM/5j/AnH
|
|
13
|
+
WHka8xMQSxJUeoXtlFT/AgMBAAGjUzBRMB0GA1UdDgQWBBRAo64Da82emF0q53wC
|
|
14
|
+
dj4wbFTmMDAfBgNVHSMEGDAWgBRAo64Da82emF0q53wCdj4wbFTmMDAPBgNVHRMB
|
|
15
|
+
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQACdVeJ/CnXpY4t568bQRxIr+t3
|
|
16
|
+
IqeIqJVg1nt6OaB/EJPJil/lvJD8bjO5W5uXAnbkv26i0Q89KFZH3xKFHRmL/zR+
|
|
17
|
+
0YPcbi3LmQ+NY6LtH1GtBlRiobROfSp8/GxTrGxqMGHHxBdY+v8lPiK1LHTE99ik
|
|
18
|
+
cl8qF+DQMGeliPQcg9rLgXagymEc9oS3OgxjL7Al4/P8bLkdC/hxk+cEUFCXUX4v
|
|
19
|
+
z/PAFnHwEeJPiiNYG+hpQQRloVncynEwhBr/pGGMdLGaFBje41kn9Ss2anc+YaiW
|
|
20
|
+
DyoDLuQkCcRrUvc2dCGMZs0KaCv8i61QWGNy8suGq6nrXW9A57JhS7ak8xg9
|
|
21
|
+
-----END CERTIFICATE-----
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
|
2
|
+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD4PWQPjCCXOvZY
|
|
3
|
+
UDpYhZHR4Nw0ErkZEZWtPMyoBnyGkYwqK1SrHCcIYQL8C22dN1rnVdFvCVH13fQH
|
|
4
|
+
z1SJ/v3jkS+9IiOUDenXpZP/G0pprTbLGh33aaQU53EqvO1T2CAAzIAVdme0cihO
|
|
5
|
+
B+KL2A5bDzP9fu+c0ddn+N++ZgcqBHMYmZKIGJv5QZ+i+/1xrGqQ/znwV7RuBGms
|
|
6
|
+
8fxA3x4Y4ELUzIHMpbXbQn95qC9u7ee4odQ5KjR1M0oocPKOYBGa/Dqnhd3Wx0lH
|
|
7
|
+
R3No8HgC4TBfxruhs+NZtRyInOTFr99TWbBx+zrDvxecM/5j/AnHWHka8xMQSxJU
|
|
8
|
+
eoXtlFT/AgMBAAECggEAeQhprIDsdP/dE4MOZ5G8XsHNa6BDWlsuCLivqi77cRsu
|
|
9
|
+
5XKaBhGkYFv4eg5SZAiUzF0Vz5cSPJf6vwISHHRUsqtbxNIL2ciM3sVO6t+SAQNg
|
|
10
|
+
x1cVDEgYJc8QaL+T0UD7nZgXzR8dgif0ydLjXL67hR9dAHhu+J2BcqKv6KPTU2LB
|
|
11
|
+
KWbCcLkY4PdCGwSNuvyPokc3Fo8U40Lm8tcWQ6sFV/+bv6+ShWhsb5Dx7sfVoijf
|
|
12
|
+
YikzFc7sEHb//nH2+C/FoCqneDPYl8lVH9zKYis4xWcA68bNwyfWpaQLXo32DKYq
|
|
13
|
+
rZxnwnawtU5b+t+TVp1DcCjOM3vaodeKI3Gqq5SjBQKBgQD/Sy33WOK+qelkvJxb
|
|
14
|
+
IcyP3U/ijNpWP3UidYGmmpTBP5UdaXyObX1G4nmfQjDad080jPpP/r350Tr0Xi2s
|
|
15
|
+
6wZBw0HoTZrZw5MQQKRQyh0Hg5VHHoHbD97UuLGGZ+Yc/hSWWfuDQHeV5Ami+VXD
|
|
16
|
+
TCYZGhtsjY9lUfUWYcsdcOA1fQKBgQD47TcVSC+3/8zKd2AAJeYrpx6wL/5X/4Zn
|
|
17
|
+
U0VeBxfF6MjQlu3avnwi6MxjkuB4KJAgWVHvJspJaS8ilIJN5qBsivphtfD0RV+d
|
|
18
|
+
UW9DCzoDUlU/AmNMD+stylgziSWoG9rzcY7P71izRE1tGGsgOXvZS5xUHzqWBwg6
|
|
19
|
+
MtYB4EQNKwKBgQCTUBVpKmBE9xTXbUKoD5vT1Df5mZ+PrzRvOvEiawa1cHQiMbGP
|
|
20
|
+
Gjz0/1CBBpfcKIaK42K5cFy9X++t/P5MTp4gqoRIgSd+yyz8buCiQc54fIRSMpdq
|
|
21
|
+
CgFiLGU8Eo4lYrQMgkXw2e1nj9vDsC698B331CnI/PKm26EaVjn3dh/anQKBgGxB
|
|
22
|
+
FWRu+TmmyBQA6EIIOVogmqr6pDz2xienQhKLOR57huGX0acAkhHIdiKTnIUE9vDq
|
|
23
|
+
h0Re9TgJw1LhjO1976RkqFDYBArnJJbQ9HcOqdMJ+kKlsjNA9QD773GyIitCueyH
|
|
24
|
+
JRluuH91o8pfBS+FcEPmqvy2fA8EzeIpe4JjWpTzAoGAQHoipuzDbhb02XMqioc+
|
|
25
|
+
UCsa6erj3kGrBUiFcvNutTG+oVq3qXOebOc9bCZcGHwsm0fgX5zk1XgY+IRfCWJY
|
|
26
|
+
X14DQfLMBi/vObDXH9BaoVvc50EPSBW0V5k2aXavJ6XelbBXSI33bzGb+5wAltI/
|
|
27
|
+
CFKf5ePEAi3jE69yYnTwbdc=
|
|
28
|
+
-----END PRIVATE KEY-----
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
206DA63B52317CCDF0AA22CB1423BEEAE6C6747B
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import ssl
|
|
3
|
+
import threading
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from testcontainers.core.network import Network
|
|
9
|
+
from testcontainers.core.waiting_utils import wait_container_is_ready
|
|
10
|
+
from testcontainers.generic import ServerContainer
|
|
11
|
+
from testcontainers.postgres import PostgresContainer
|
|
12
|
+
|
|
13
|
+
from pyrelukko import RelukkoClient
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR = Path(__file__).parent.absolute()
|
|
16
|
+
INITDB_DIR = SCRIPT_DIR / "initdb.d"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RelukkoContainer(ServerContainer):
|
|
20
|
+
def __init__(self, net: Network,
|
|
21
|
+
image="registry.gitlab.com/relukko/relukko:0.9.0", db_url=None):
|
|
22
|
+
self.db_url = db_url
|
|
23
|
+
self.net = net
|
|
24
|
+
super(RelukkoContainer, self).__init__(image=image, port=3000)
|
|
25
|
+
|
|
26
|
+
def _configure(self):
|
|
27
|
+
self.with_env("DATABASE_URL", self.db_url)
|
|
28
|
+
self.with_env("RELUKKO_API_KEY", "somekey")
|
|
29
|
+
self.with_env("RELUKKO_USER", "relukko")
|
|
30
|
+
self.with_env("RELUKKO_PASSWORD", "relukko")
|
|
31
|
+
self.with_env("RELUKKO_BIND_ADDR", "0.0.0.0")
|
|
32
|
+
self.with_network(self.net)
|
|
33
|
+
|
|
34
|
+
def get_api_url(self) -> str:
|
|
35
|
+
return f"http://localhost:{self.get_exposed_port(3000)}"
|
|
36
|
+
|
|
37
|
+
def _create_connection_url(self) -> str:
|
|
38
|
+
return f"{self.get_api_url()}/healthchecker"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RelukkoDbContainer(PostgresContainer):
|
|
42
|
+
def __init__(self, net: Network, image: str = "postgres:latest", port: int = 5432, username: str | None = None, password: str | None = None, dbname: str | None = None, driver: str | None = "psycopg2", **kwargs) -> None:
|
|
43
|
+
self.net = net
|
|
44
|
+
super().__init__(image, port, username, password, dbname, driver, **kwargs)
|
|
45
|
+
|
|
46
|
+
def _configure(self) -> None:
|
|
47
|
+
self.with_volume_mapping(INITDB_DIR, "/docker-entrypoint-initdb.d", "Z")
|
|
48
|
+
self.with_env("POSTGRES_USER", "relukko")
|
|
49
|
+
self.with_env("POSTGRES_PASSWORD", "relukko")
|
|
50
|
+
self.with_env("POSTGRES_DB", "relukko")
|
|
51
|
+
self.with_network(self.net)
|
|
52
|
+
|
|
53
|
+
@wait_container_is_ready()
|
|
54
|
+
def _connect(self) -> None:
|
|
55
|
+
packet = bytes([
|
|
56
|
+
0x00, 0x00, 0x00, 0x52, 0x00, 0x03, 0x00, 0x00,
|
|
57
|
+
0x75, 0x73, 0x65, 0x72, 0x00, 0x72, 0x65, 0x6c,
|
|
58
|
+
0x75, 0x6b, 0x6b, 0x6f, 0x00, 0x64, 0x61, 0x74,
|
|
59
|
+
0x61, 0x62, 0x61, 0x73, 0x65, 0x00, 0x72, 0x65,
|
|
60
|
+
0x6c, 0x75, 0x6b, 0x6b, 0x6f, 0x00, 0x61, 0x70,
|
|
61
|
+
0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
|
62
|
+
0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x70,
|
|
63
|
+
0x73, 0x71, 0x6c, 0x00, 0x63, 0x6c, 0x69, 0x65,
|
|
64
|
+
0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64,
|
|
65
|
+
0x69, 0x6e, 0x67, 0x00, 0x55, 0x54, 0x46, 0x38,
|
|
66
|
+
0x00, 0x00
|
|
67
|
+
])
|
|
68
|
+
port = self.get_exposed_port(self.port)
|
|
69
|
+
with socket.create_connection(("localhost", port)) as sock:
|
|
70
|
+
sock.send(packet)
|
|
71
|
+
buf = sock.recv(40)
|
|
72
|
+
if len(buf) == 0 and "SCRAM-SHA" not in buf:
|
|
73
|
+
raise ConnectionError
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.fixture(scope="session")
|
|
77
|
+
def relukko_backend():
|
|
78
|
+
with Network() as rl_net:
|
|
79
|
+
with RelukkoDbContainer(net=rl_net,
|
|
80
|
+
image="postgres:16", hostname="relukkodb") as _db:
|
|
81
|
+
db_url = "postgresql://relukko:relukko@relukkodb/relukko"
|
|
82
|
+
with RelukkoContainer(rl_net, db_url=db_url) as backend:
|
|
83
|
+
relukko = RelukkoClient(
|
|
84
|
+
base_url=backend.get_api_url(), api_key="somekey")
|
|
85
|
+
yield relukko, backend
|
|
86
|
+
|
|
87
|
+
@pytest.fixture(scope="function")
|
|
88
|
+
def tls_listener():
|
|
89
|
+
|
|
90
|
+
certfile = SCRIPT_DIR / "cert" / "relukko.crt"
|
|
91
|
+
keyfile = SCRIPT_DIR / "cert" / "relukko.key"
|
|
92
|
+
|
|
93
|
+
def run_server(port_info: List, keep_running: threading.Event):
|
|
94
|
+
# Create a TCP socket
|
|
95
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
|
|
96
|
+
sock.bind(("127.0.0.1", 0))
|
|
97
|
+
sock.listen(5) # Listen for incoming connections
|
|
98
|
+
|
|
99
|
+
_, assigned_port = sock.getsockname()
|
|
100
|
+
port_info.append(assigned_port)
|
|
101
|
+
|
|
102
|
+
# Wrap the socket with TLS
|
|
103
|
+
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
|
104
|
+
context.load_cert_chain(certfile=certfile, keyfile=keyfile)
|
|
105
|
+
with context.wrap_socket(sock, server_side=True) as ssock:
|
|
106
|
+
|
|
107
|
+
ssock.settimeout(1)
|
|
108
|
+
|
|
109
|
+
while keep_running.is_set():
|
|
110
|
+
try:
|
|
111
|
+
client_socket, _ = ssock.accept()
|
|
112
|
+
client_socket.sendall(b"Hello, TLS Client!")
|
|
113
|
+
client_socket.close()
|
|
114
|
+
except Exception as _:
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
port_info = []
|
|
118
|
+
keep_running = threading.Event()
|
|
119
|
+
keep_running.set()
|
|
120
|
+
thread = threading.Thread(target=run_server, args=(port_info, keep_running))
|
|
121
|
+
thread.start()
|
|
122
|
+
|
|
123
|
+
while not port_info:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
yield thread, port_info[0]
|
|
127
|
+
|
|
128
|
+
keep_running.clear()
|
|
129
|
+
thread.join()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- Add up migration script here
|
|
2
|
+
CREATE EXTENSION moddatetime;
|
|
3
|
+
|
|
4
|
+
CREATE TABLE locks (
|
|
5
|
+
id uuid DEFAULT gen_random_uuid(),
|
|
6
|
+
lock_name VARCHAR NOT NULL,
|
|
7
|
+
creator VARCHAR,
|
|
8
|
+
ip INET,
|
|
9
|
+
expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + (10 ||' minutes')::interval ,
|
|
10
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
11
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
12
|
+
PRIMARY KEY (id),
|
|
13
|
+
UNIQUE(lock_name)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TRIGGER locks_moddatetime
|
|
17
|
+
BEFORE UPDATE ON locks
|
|
18
|
+
FOR EACH ROW
|
|
19
|
+
EXECUTE PROCEDURE moddatetime (updated_at);
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
import ssl
|
|
4
|
+
import socket
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
import requests
|
|
11
|
+
from requests.adapters import HTTPAdapter
|
|
12
|
+
from requests.cookies import cookiejar_from_dict
|
|
13
|
+
from urllib3.util import parse_url
|
|
14
|
+
|
|
15
|
+
from pyrelukko import RelukkoClient
|
|
16
|
+
from pyrelukko.pyrelukko import RelukkoDoRetry
|
|
17
|
+
from pyrelukko.retry import retry
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR = Path(__file__).parent.absolute()
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@retry(None, ConnectionRefusedError)
|
|
26
|
+
def _check_tls(ssl_ctx: ssl.SSLContext, port):
|
|
27
|
+
with socket.create_connection(("127.0.0.1", port)) as sock:
|
|
28
|
+
with ssl_ctx.wrap_socket(sock, server_hostname="relukko") as ssock:
|
|
29
|
+
response = ssock.recv(20)
|
|
30
|
+
assert response == b"Hello, TLS Client!"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _check_has_serial_no(ssl_ctx: ssl.SSLContext):
|
|
34
|
+
serial_numbers = [x["serialNumber"] for x in ssl_ctx.get_ca_certs()]
|
|
35
|
+
assert "78B4E1E498136651B475BC2D0880A28E4835316C" in serial_numbers
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_init_relukko_client():
|
|
39
|
+
relukko = RelukkoClient(base_url="", api_key="")
|
|
40
|
+
|
|
41
|
+
assert isinstance(relukko, RelukkoClient)
|
|
42
|
+
assert relukko.session.trust_env == True
|
|
43
|
+
assert relukko.session.cookies._cookies == {}
|
|
44
|
+
assert relukko.ssl_ctx is None
|
|
45
|
+
http_adapter: HTTPAdapter = relukko.session.adapters.get("http://")
|
|
46
|
+
assert http_adapter.max_retries.total == 0
|
|
47
|
+
assert http_adapter.max_retries.connect is None
|
|
48
|
+
assert http_adapter.max_retries.read == False
|
|
49
|
+
assert http_adapter.max_retries.redirect is None
|
|
50
|
+
assert http_adapter.max_retries.status is None
|
|
51
|
+
assert http_adapter.max_retries.other is None
|
|
52
|
+
assert http_adapter.max_retries.backoff_factor == 0
|
|
53
|
+
assert http_adapter.max_retries.backoff_max == 120
|
|
54
|
+
assert http_adapter.max_retries.backoff_jitter == 0.0
|
|
55
|
+
assert http_adapter.max_retries.raise_on_redirect == True
|
|
56
|
+
assert http_adapter.max_retries.raise_on_status == True
|
|
57
|
+
assert relukko.tries == 4
|
|
58
|
+
assert relukko.delay == 5
|
|
59
|
+
assert relukko.backoff == 2.0
|
|
60
|
+
assert relukko.max_delay is None
|
|
61
|
+
assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
|
|
62
|
+
|
|
63
|
+
with pytest.raises(ValueError):
|
|
64
|
+
RelukkoClient(base_url=1000, api_key="")
|
|
65
|
+
|
|
66
|
+
def test_init_relukko_client_with_env_var(tls_listener):
|
|
67
|
+
_, port = tls_listener
|
|
68
|
+
|
|
69
|
+
capath = SCRIPT_DIR / "cert"
|
|
70
|
+
cafile = capath / "rootCA.crt"
|
|
71
|
+
|
|
72
|
+
pytest.MonkeyPatch().setenv("REQUESTS_CA_BUNDLE", str(cafile.absolute()))
|
|
73
|
+
relukko = RelukkoClient(base_url="https://relukko", api_key="")
|
|
74
|
+
pytest.MonkeyPatch().delenv("REQUESTS_CA_BUNDLE")
|
|
75
|
+
_check_tls(relukko.ssl_ctx, port)
|
|
76
|
+
_check_has_serial_no(relukko.ssl_ctx)
|
|
77
|
+
|
|
78
|
+
pytest.MonkeyPatch().setenv("CURL_CA_BUNDLE", str(cafile.absolute()))
|
|
79
|
+
relukko2 = RelukkoClient(base_url="https://relukko", api_key="")
|
|
80
|
+
pytest.MonkeyPatch().delenv("CURL_CA_BUNDLE")
|
|
81
|
+
_check_tls(relukko2.ssl_ctx, port)
|
|
82
|
+
_check_has_serial_no(relukko2.ssl_ctx)
|
|
83
|
+
|
|
84
|
+
pytest.MonkeyPatch().setenv("REQUESTS_CA_BUNDLE", str(capath.absolute()))
|
|
85
|
+
relukko3 = RelukkoClient(base_url="https://relukko", api_key="")
|
|
86
|
+
pytest.MonkeyPatch().delenv("REQUESTS_CA_BUNDLE")
|
|
87
|
+
_check_tls(relukko3.ssl_ctx, port)
|
|
88
|
+
_check_has_serial_no(relukko3.ssl_ctx)
|
|
89
|
+
|
|
90
|
+
pytest.MonkeyPatch().setenv("CURL_CA_BUNDLE", str(capath.absolute()))
|
|
91
|
+
relukko4 = RelukkoClient(base_url="https://relukko", api_key="")
|
|
92
|
+
pytest.MonkeyPatch().delenv("CURL_CA_BUNDLE")
|
|
93
|
+
_check_tls(relukko4.ssl_ctx, port)
|
|
94
|
+
_check_has_serial_no(relukko4.ssl_ctx)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_init_relukko_client_with_cafile(tls_listener):
|
|
98
|
+
_, port = tls_listener
|
|
99
|
+
|
|
100
|
+
cafile = SCRIPT_DIR / "cert" / "rootCA.crt"
|
|
101
|
+
|
|
102
|
+
relukko = RelukkoClient(
|
|
103
|
+
base_url="https://relukko", api_key="", cafile=cafile)
|
|
104
|
+
_check_tls(relukko.ssl_ctx, port)
|
|
105
|
+
_check_has_serial_no(relukko.ssl_ctx)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_init_relukko_client_with_capath(tls_listener):
|
|
109
|
+
_, port = tls_listener
|
|
110
|
+
|
|
111
|
+
capath = SCRIPT_DIR / "cert"
|
|
112
|
+
|
|
113
|
+
relukko = RelukkoClient(
|
|
114
|
+
base_url="https://relukko", api_key="", capath=capath)
|
|
115
|
+
_check_tls(relukko.ssl_ctx, port)
|
|
116
|
+
_check_has_serial_no(relukko.ssl_ctx)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_init_relukko_client_with_cadata(tls_listener):
|
|
120
|
+
_, port = tls_listener
|
|
121
|
+
|
|
122
|
+
cafile = SCRIPT_DIR / "cert" / "rootCA.crt"
|
|
123
|
+
cadata = cafile.read_text()
|
|
124
|
+
|
|
125
|
+
relukko = RelukkoClient(
|
|
126
|
+
base_url="https://relukko", api_key="", cadata=cadata)
|
|
127
|
+
_check_tls(relukko.ssl_ctx, port)
|
|
128
|
+
_check_has_serial_no(relukko.ssl_ctx)
|
|
129
|
+
|
|
130
|
+
cader = SCRIPT_DIR / "cert" / "rootCA.der"
|
|
131
|
+
cadata = cader.read_bytes()
|
|
132
|
+
|
|
133
|
+
relukko2 = RelukkoClient(
|
|
134
|
+
base_url="https://relukko", api_key="", cadata=cadata)
|
|
135
|
+
_check_tls(relukko2.ssl_ctx, port)
|
|
136
|
+
_check_has_serial_no(relukko2.ssl_ctx)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_check_tls_throws_error(tls_listener):
|
|
140
|
+
_, port = tls_listener
|
|
141
|
+
|
|
142
|
+
relukko = RelukkoClient(
|
|
143
|
+
base_url="https://relukko", api_key="")
|
|
144
|
+
with pytest.raises(ssl.SSLCertVerificationError):
|
|
145
|
+
_check_tls(relukko.ssl_ctx, port)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_reconfigure_relukko_client():
|
|
149
|
+
relukko = RelukkoClient(base_url="", api_key="")
|
|
150
|
+
|
|
151
|
+
relukko.reconfigure_relukko(
|
|
152
|
+
"https://relukko", api_key="secret-key")
|
|
153
|
+
|
|
154
|
+
assert relukko.base_url == parse_url("https://relukko")
|
|
155
|
+
assert relukko.ws_url == parse_url("wss://relukko/deletions")
|
|
156
|
+
assert relukko.session.headers['X-api-Key'] == "secret-key"
|
|
157
|
+
|
|
158
|
+
default_ctx = ssl.create_default_context()
|
|
159
|
+
assert relukko.ssl_ctx.check_hostname == default_ctx.check_hostname
|
|
160
|
+
assert relukko.ssl_ctx.hostname_checks_common_name == \
|
|
161
|
+
default_ctx.hostname_checks_common_name
|
|
162
|
+
assert relukko.ssl_ctx.keylog_filename == default_ctx.keylog_filename
|
|
163
|
+
assert relukko.ssl_ctx.sni_callback == default_ctx.sni_callback
|
|
164
|
+
assert relukko.ssl_ctx.verify_flags == default_ctx.verify_flags
|
|
165
|
+
assert relukko.ssl_ctx.verify_mode == default_ctx.verify_mode
|
|
166
|
+
assert relukko.ssl_ctx.options == default_ctx.options
|
|
167
|
+
assert relukko.tries == 4
|
|
168
|
+
assert relukko.delay == 5
|
|
169
|
+
assert relukko.backoff == 2.0
|
|
170
|
+
assert relukko.max_delay is None
|
|
171
|
+
assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_reconfigure_relukko_client_extended():
|
|
175
|
+
relukko = RelukkoClient(base_url="", api_key="")
|
|
176
|
+
|
|
177
|
+
cookies = cookiejar_from_dict({"cookie1": "value1"})
|
|
178
|
+
verify_mode = ssl.VerifyMode.CERT_NONE
|
|
179
|
+
verify_flags = ssl.VerifyFlags.VERIFY_DEFAULT
|
|
180
|
+
options = ssl.Options.OP_ALL
|
|
181
|
+
|
|
182
|
+
relukko.reconfigure_relukko(
|
|
183
|
+
base_url="https://relukko", api_key="my-API-key", trust_env=False,
|
|
184
|
+
cookies=cookies, total=100, connect=99, read=98, redirect=97,
|
|
185
|
+
status=96, other=95, backoff_factor=94, backoff_max=1000,
|
|
186
|
+
backoff_jitter=93, raise_on_redirect=False, raise_on_status=False,
|
|
187
|
+
check_hostname=False, hostname_checks_common_name=False,
|
|
188
|
+
verify_mode=verify_mode, verify_flags=verify_flags, options=options
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
assert relukko.session.trust_env == False
|
|
192
|
+
assert relukko.session.cookies == cookies
|
|
193
|
+
assert relukko.base_url == parse_url("https://relukko")
|
|
194
|
+
assert relukko.ws_url == parse_url("wss://relukko/deletions")
|
|
195
|
+
assert relukko.session.headers['X-api-Key'] == "my-API-key"
|
|
196
|
+
http_adapter: HTTPAdapter = relukko.session.adapters.get("http://")
|
|
197
|
+
assert http_adapter.max_retries.total == 100
|
|
198
|
+
assert http_adapter.max_retries.connect == 99
|
|
199
|
+
assert http_adapter.max_retries.read == 98
|
|
200
|
+
assert http_adapter.max_retries.redirect == 97
|
|
201
|
+
assert http_adapter.max_retries.status == 96
|
|
202
|
+
assert http_adapter.max_retries.other == 95
|
|
203
|
+
assert http_adapter.max_retries.backoff_factor == 94.0
|
|
204
|
+
assert http_adapter.max_retries.backoff_max == 1000.0
|
|
205
|
+
assert http_adapter.max_retries.backoff_jitter == 93.0
|
|
206
|
+
assert http_adapter.max_retries.raise_on_redirect == False
|
|
207
|
+
assert http_adapter.max_retries.raise_on_status == False
|
|
208
|
+
assert isinstance(relukko.ssl_ctx, ssl.SSLContext)
|
|
209
|
+
assert relukko.ssl_ctx.check_hostname == False
|
|
210
|
+
assert relukko.ssl_ctx.hostname_checks_common_name == False
|
|
211
|
+
assert relukko.ssl_ctx.keylog_filename is None
|
|
212
|
+
assert relukko.ssl_ctx.sni_callback is None
|
|
213
|
+
assert relukko.ssl_ctx.verify_flags == verify_flags
|
|
214
|
+
assert relukko.ssl_ctx.verify_mode == verify_mode
|
|
215
|
+
assert relukko.ssl_ctx.options == options
|
|
216
|
+
assert relukko.tries == 4
|
|
217
|
+
assert relukko.delay == 5
|
|
218
|
+
assert relukko.backoff == 2.0
|
|
219
|
+
assert relukko.max_delay is None
|
|
220
|
+
assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_init_relukko_client_extented():
|
|
224
|
+
cookies = cookiejar_from_dict({"cookie1": "value1"})
|
|
225
|
+
|
|
226
|
+
relukko = RelukkoClient(
|
|
227
|
+
base_url="http://relukko", api_key="my-API-key", trust_env=False,
|
|
228
|
+
cookies=cookies, total=100, connect=99, read=98, redirect=97,
|
|
229
|
+
status=96, other=95, backoff_factor=94, backoff_max=1000,
|
|
230
|
+
backoff_jitter=93, raise_on_redirect=False, raise_on_status=False,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
assert relukko.session.trust_env == False
|
|
234
|
+
assert relukko.session.cookies == cookies
|
|
235
|
+
assert relukko.base_url == parse_url("http://relukko")
|
|
236
|
+
assert relukko.ws_url == parse_url("ws://relukko/deletions")
|
|
237
|
+
assert relukko.session.headers['X-api-Key'] == "my-API-key"
|
|
238
|
+
assert relukko.ssl_ctx is None
|
|
239
|
+
http_adapter: HTTPAdapter = relukko.session.adapters.get("http://")
|
|
240
|
+
assert http_adapter.max_retries.total == 100
|
|
241
|
+
assert http_adapter.max_retries.connect == 99
|
|
242
|
+
assert http_adapter.max_retries.read == 98
|
|
243
|
+
assert http_adapter.max_retries.redirect == 97
|
|
244
|
+
assert http_adapter.max_retries.status == 96
|
|
245
|
+
assert http_adapter.max_retries.other == 95
|
|
246
|
+
assert http_adapter.max_retries.backoff_factor == 94.0
|
|
247
|
+
assert http_adapter.max_retries.backoff_max == 1000.0
|
|
248
|
+
assert http_adapter.max_retries.backoff_jitter == 93.0
|
|
249
|
+
assert http_adapter.max_retries.raise_on_redirect == False
|
|
250
|
+
assert http_adapter.max_retries.raise_on_status == False
|
|
251
|
+
assert relukko.tries == 4
|
|
252
|
+
assert relukko.delay == 5
|
|
253
|
+
assert relukko.backoff == 2.0
|
|
254
|
+
assert relukko.max_delay is None
|
|
255
|
+
assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_init_relukko_client_ssl_ctx():
|
|
259
|
+
relukko = RelukkoClient(
|
|
260
|
+
base_url="https://relukko", api_key="my-API-key",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
default_ctx = ssl.create_default_context()
|
|
264
|
+
|
|
265
|
+
assert isinstance(relukko.ssl_ctx, ssl.SSLContext)
|
|
266
|
+
assert relukko.ssl_ctx.check_hostname == default_ctx.check_hostname
|
|
267
|
+
assert relukko.ssl_ctx.hostname_checks_common_name == \
|
|
268
|
+
default_ctx.hostname_checks_common_name
|
|
269
|
+
assert relukko.ssl_ctx.keylog_filename == default_ctx.keylog_filename
|
|
270
|
+
assert relukko.ssl_ctx.sni_callback == default_ctx.sni_callback
|
|
271
|
+
assert relukko.ssl_ctx.verify_flags == default_ctx.verify_flags
|
|
272
|
+
assert relukko.ssl_ctx.verify_mode == default_ctx.verify_mode
|
|
273
|
+
assert relukko.ssl_ctx.options == default_ctx.options
|
|
274
|
+
assert relukko.tries == 4
|
|
275
|
+
assert relukko.delay == 5
|
|
276
|
+
assert relukko.backoff == 2.0
|
|
277
|
+
assert relukko.max_delay is None
|
|
278
|
+
assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def test_init_relukko_client_ssl_ctx_extended():
|
|
282
|
+
verify_mode = ssl.VerifyMode.CERT_NONE
|
|
283
|
+
verify_flags = ssl.VerifyFlags.VERIFY_DEFAULT
|
|
284
|
+
options = ssl.Options.OP_ALL
|
|
285
|
+
|
|
286
|
+
relukko = RelukkoClient(
|
|
287
|
+
base_url="https://relukko", api_key="my-API-key", check_hostname=False,
|
|
288
|
+
hostname_checks_common_name=False, verify_mode=verify_mode,
|
|
289
|
+
verify_flags=verify_flags, options=options
|
|
290
|
+
)
|
|
291
|
+
assert isinstance(relukko.ssl_ctx, ssl.SSLContext)
|
|
292
|
+
assert relukko.ssl_ctx.check_hostname == False
|
|
293
|
+
assert relukko.ssl_ctx.hostname_checks_common_name == False
|
|
294
|
+
assert relukko.ssl_ctx.keylog_filename is None
|
|
295
|
+
assert relukko.ssl_ctx.sni_callback is None
|
|
296
|
+
assert relukko.ssl_ctx.verify_flags == verify_flags
|
|
297
|
+
assert relukko.ssl_ctx.verify_mode == verify_mode
|
|
298
|
+
assert relukko.ssl_ctx.options == options
|
|
299
|
+
assert relukko.tries == 4
|
|
300
|
+
assert relukko.delay == 5
|
|
301
|
+
assert relukko.backoff == 2.0
|
|
302
|
+
assert relukko.max_delay is None
|
|
303
|
+
assert relukko.exceptions == (requests.ConnectionError, RelukkoDoRetry)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def test_init_relukko_client_retry():
|
|
307
|
+
|
|
308
|
+
exceptions = (
|
|
309
|
+
ValueError,
|
|
310
|
+
ConnectionRefusedError,
|
|
311
|
+
ConnectionResetError,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
relukko = RelukkoClient(
|
|
315
|
+
base_url="", api_key="", tries=100, delay=99, backoff=98.7,
|
|
316
|
+
max_delay=97, exceptions=exceptions)
|
|
317
|
+
|
|
318
|
+
assert relukko.tries == 100
|
|
319
|
+
assert relukko.delay == 99
|
|
320
|
+
assert relukko.backoff == 98.7
|
|
321
|
+
assert relukko.max_delay == 97
|
|
322
|
+
assert relukko.exceptions == exceptions
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def test_reconfigre_relukko_client_retry():
|
|
326
|
+
relukko = RelukkoClient(base_url="", api_key="")
|
|
327
|
+
|
|
328
|
+
exceptions = (
|
|
329
|
+
ValueError,
|
|
330
|
+
ConnectionRefusedError,
|
|
331
|
+
ConnectionResetError,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
relukko.reconfigure_relukko(
|
|
335
|
+
tries=100, delay=99, backoff=98.7,
|
|
336
|
+
max_delay=97, exceptions=exceptions)
|
|
337
|
+
|
|
338
|
+
assert relukko.tries == 100
|
|
339
|
+
assert relukko.delay == 99
|
|
340
|
+
assert relukko.backoff == 98.7
|
|
341
|
+
assert relukko.max_delay == 97
|
|
342
|
+
assert relukko.exceptions == exceptions
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_acquire_relukko(relukko_backend):
|
|
346
|
+
relukko, _ = relukko_backend
|
|
347
|
+
|
|
348
|
+
lock = relukko.acquire_relukko("pylock", "pytest", 10)
|
|
349
|
+
assert isinstance(lock, Dict)
|
|
350
|
+
start_time = time.time()
|
|
351
|
+
lock = relukko.acquire_relukko("pylock", "pytest", 30)
|
|
352
|
+
end_time = time.time()
|
|
353
|
+
assert lock is None
|
|
354
|
+
assert 29 < end_time - start_time < 34
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def test_delete_relukko(relukko_backend):
|
|
358
|
+
relukko, _ = relukko_backend
|
|
359
|
+
|
|
360
|
+
lock = relukko.acquire_relukko("del_lock", "pytest", 10)
|
|
361
|
+
del_lock = relukko.delete_relukko(lock['id'])
|
|
362
|
+
assert lock == del_lock
|
|
363
|
+
|
|
364
|
+
get_lock = relukko.get_lock(lock['id'])
|
|
365
|
+
assert get_lock['status'] == 422
|
|
366
|
+
|
|
367
|
+
locks = relukko.get_locks()
|
|
368
|
+
assert lock not in locks
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_get_relukko(relukko_backend):
|
|
372
|
+
relukko, _ = relukko_backend
|
|
373
|
+
|
|
374
|
+
lock = relukko.acquire_relukko("get_lock", "pytest", 10)
|
|
375
|
+
assert isinstance(lock, Dict)
|
|
376
|
+
|
|
377
|
+
get_lock = relukko.get_lock(lock['id'])
|
|
378
|
+
assert get_lock == lock
|
|
379
|
+
|
|
380
|
+
locks = relukko.get_locks()
|
|
381
|
+
assert lock in locks
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def test_update_relukko_creator(relukko_backend):
|
|
385
|
+
relukko, _ = relukko_backend
|
|
386
|
+
|
|
387
|
+
lock = relukko.acquire_relukko("update_lock", "pytest", 10)
|
|
388
|
+
assert lock['creator'] == "pytest"
|
|
389
|
+
|
|
390
|
+
upd_lock = relukko.update_relukko(lock['id'], creator="tsetyp")
|
|
391
|
+
assert upd_lock['creator'] == "tsetyp"
|
|
392
|
+
|
|
393
|
+
get_lock = relukko.get_lock(lock['id'])
|
|
394
|
+
assert get_lock['creator'] == "tsetyp"
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def test_update_relukko_expires_at(relukko_backend):
|
|
398
|
+
relukko, _ = relukko_backend
|
|
399
|
+
|
|
400
|
+
lock = relukko.acquire_relukko("update_lock_exp", "pytest", 10)
|
|
401
|
+
id = lock['id']
|
|
402
|
+
|
|
403
|
+
expires_at = datetime.fromisoformat("2099-12-31T12:34:56.789Z")
|
|
404
|
+
upd_lock = relukko.update_relukko(id, expires_at=expires_at)
|
|
405
|
+
assert upd_lock['expires_at'] == "2099-12-31T12:34:56.789Z"
|
|
406
|
+
|
|
407
|
+
with pytest.raises(ValueError):
|
|
408
|
+
relukko.update_relukko(id, expires_at="2099-12-01T23:12Z")
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def test_keep_relukko_alive(relukko_backend):
|
|
412
|
+
relukko, _ = relukko_backend
|
|
413
|
+
|
|
414
|
+
lock = relukko.acquire_relukko("keep_me", "pytest", 10)
|
|
415
|
+
id = lock['id']
|
|
416
|
+
|
|
417
|
+
start_time = datetime.now(timezone.utc)
|
|
418
|
+
keep_lock = relukko.keep_relukko_alive(id)
|
|
419
|
+
assert lock['expires_at'] is not keep_lock['expires_at']
|
|
420
|
+
|
|
421
|
+
expires_at = datetime.fromisoformat(keep_lock['expires_at'])
|
|
422
|
+
|
|
423
|
+
expires_diff = expires_at - start_time
|
|
424
|
+
assert 295 < expires_diff.seconds < 305
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_keep_relukko_alive_put(relukko_backend):
|
|
428
|
+
relukko, _ = relukko_backend
|
|
429
|
+
|
|
430
|
+
lock = relukko.acquire_relukko("keep_me_more", "pytest", 10)
|
|
431
|
+
id = lock['id']
|
|
432
|
+
|
|
433
|
+
start_time = datetime.now(timezone.utc)
|
|
434
|
+
keep_lock = relukko.keep_relukko_alive_put(id, 3600)
|
|
435
|
+
assert lock['expires_at'] is not keep_lock['expires_at']
|
|
436
|
+
|
|
437
|
+
expires_at = datetime.fromisoformat(keep_lock['expires_at'])
|
|
438
|
+
|
|
439
|
+
expires_diff = expires_at - start_time
|
|
440
|
+
assert 3595 < expires_diff.seconds < 3605
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def test_add_to_expires_at_time(relukko_backend):
|
|
444
|
+
relukko, _ = relukko_backend
|
|
445
|
+
lock = relukko.acquire_relukko("add_me_up", "pytest", 10)
|
|
446
|
+
id = lock['id']
|
|
447
|
+
start_expires_at = datetime.fromisoformat(lock['expires_at'])
|
|
448
|
+
|
|
449
|
+
add_lock = relukko.add_to_expires_at_time(id)
|
|
450
|
+
assert lock['expires_at'] is not add_lock['expires_at']
|
|
451
|
+
|
|
452
|
+
add_expires_at = datetime.fromisoformat(add_lock['expires_at'])
|
|
453
|
+
|
|
454
|
+
expires_diff = add_expires_at - start_expires_at
|
|
455
|
+
assert expires_diff.seconds == 300
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def test_add_to_expires_at_time_put(relukko_backend):
|
|
459
|
+
relukko, _ = relukko_backend
|
|
460
|
+
lock = relukko.acquire_relukko("add_me_up_more", "pytest", 10)
|
|
461
|
+
id = lock['id']
|
|
462
|
+
start_expires_at = datetime.fromisoformat(lock['expires_at'])
|
|
463
|
+
|
|
464
|
+
add_lock = relukko.add_to_expires_at_time_put(id, 3600)
|
|
465
|
+
assert lock['expires_at'] is not add_lock['expires_at']
|
|
466
|
+
|
|
467
|
+
add_expires_at = datetime.fromisoformat(add_lock['expires_at'])
|
|
468
|
+
|
|
469
|
+
expires_diff = add_expires_at - start_expires_at
|
|
470
|
+
assert expires_diff.seconds == 3600
|
pyrelukko-0.1.0/.pylintrc.toml
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|