ominfra 0.0.0.dev10__tar.gz → 0.0.0.dev11__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.
- {ominfra-0.0.0.dev10/ominfra.egg-info → ominfra-0.0.0.dev11}/PKG-INFO +2 -2
- ominfra-0.0.0.dev11/README.rst +5 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/__about__.py +1 -1
- ominfra-0.0.0.dev11/ominfra/clouds/aws/auth.py +222 -0
- ominfra-0.0.0.dev11/ominfra/pyremote/__init__.py +0 -0
- ominfra-0.0.0.dev11/ominfra/tools/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11/ominfra.egg-info}/PKG-INFO +2 -2
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra.egg-info/SOURCES.txt +3 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra.egg-info/requires.txt +1 -1
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/pyproject.toml +4 -4
- ominfra-0.0.0.dev10/README.rst +0 -1
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/LICENSE +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/MANIFEST.in +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/bootstrap/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/bootstrap/bootstrap.py +0 -0
- {ominfra-0.0.0.dev10/ominfra/deploy → ominfra-0.0.0.dev11/ominfra/clouds}/__init__.py +0 -0
- {ominfra-0.0.0.dev10/ominfra/deploy/executor/concerns → ominfra-0.0.0.dev11/ominfra/clouds/aws}/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/cmds.py +0 -0
- {ominfra-0.0.0.dev10/ominfra/pyremote → ominfra-0.0.0.dev11/ominfra/deploy}/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/_executor.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/configs.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/base.py +0 -0
- {ominfra-0.0.0.dev10/ominfra/tools → ominfra-0.0.0.dev11/ominfra/deploy/executor/concerns}/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/dirs.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/nginx.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/repo.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/supervisor.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/systemd.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/user.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/venv.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/main.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/__init__.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/_main.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/base.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/configs.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/deploy.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/main.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/nginx.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/repo.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/runtime.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/site.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/supervisor.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/venv.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/deploy/remote.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/pyremote/_runcommands.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/pyremote/bootstrap.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/pyremote/runcommands.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/ssh.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra/tools/listresources.py +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra.egg-info/dependency_links.txt +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/ominfra.egg-info/top_level.txt +0 -0
- {ominfra-0.0.0.dev10 → ominfra-0.0.0.dev11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev11
|
4
4
|
Summary: ominfra
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omlish==0.0.0.dev11
|
16
16
|
Provides-Extra: all
|
17
17
|
Requires-Dist: paramiko>=3.4; extra == "all"
|
18
18
|
Requires-Dist: asyncssh>=2.16; python_version < "3.13" and extra == "all"
|
@@ -0,0 +1,222 @@
|
|
1
|
+
"""
|
2
|
+
https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html
|
3
|
+
|
4
|
+
TODO:
|
5
|
+
- https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
|
6
|
+
- boto / s3transfer upload_fileobj doesn't stream either lol - eagerly calcs Content-MD5
|
7
|
+
- sts tokens
|
8
|
+
- !! fix canonical_qs - sort params
|
9
|
+
"""
|
10
|
+
import dataclasses as dc
|
11
|
+
import datetime
|
12
|
+
import hashlib
|
13
|
+
import hmac
|
14
|
+
import typing as ta
|
15
|
+
import urllib.parse
|
16
|
+
|
17
|
+
from omlish import check
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
HttpMap: ta.TypeAlias = ta.Mapping[str, ta.Sequence[str]]
|
24
|
+
|
25
|
+
|
26
|
+
def make_http_map(*kvs: tuple[str, str]) -> HttpMap:
|
27
|
+
out: dict[str, list[str]] = {}
|
28
|
+
for k, v in kvs:
|
29
|
+
out.setdefault(k, []).append(v)
|
30
|
+
return out
|
31
|
+
|
32
|
+
|
33
|
+
#
|
34
|
+
|
35
|
+
@dc.dataclass(frozen=True)
|
36
|
+
class Credentials:
|
37
|
+
access_key: str
|
38
|
+
secret_key: str = dc.field(repr=False)
|
39
|
+
|
40
|
+
|
41
|
+
@dc.dataclass(frozen=True)
|
42
|
+
class Request:
|
43
|
+
method: str
|
44
|
+
url: str
|
45
|
+
headers: HttpMap = dc.field(default_factory=dict)
|
46
|
+
payload: bytes = b''
|
47
|
+
|
48
|
+
|
49
|
+
##
|
50
|
+
|
51
|
+
|
52
|
+
def _host_from_url(url: str) -> str:
|
53
|
+
url_parts = urllib.parse.urlsplit(url)
|
54
|
+
host = check.non_empty_str(url_parts.hostname)
|
55
|
+
default_ports = {
|
56
|
+
'http': 80,
|
57
|
+
'https': 443,
|
58
|
+
}
|
59
|
+
if url_parts.port is not None:
|
60
|
+
if url_parts.port != default_ports.get(url_parts.scheme):
|
61
|
+
host = '%s:%d' % (host, url_parts.port)
|
62
|
+
return host
|
63
|
+
|
64
|
+
|
65
|
+
def _as_bytes(data: str | bytes) -> bytes:
|
66
|
+
return data if isinstance(data, bytes) else data.encode('utf-8')
|
67
|
+
|
68
|
+
|
69
|
+
def _sha256(data: str | bytes) -> str:
|
70
|
+
return hashlib.sha256(_as_bytes(data)).hexdigest()
|
71
|
+
|
72
|
+
|
73
|
+
def _sha256_sign(key: bytes, msg: str | bytes) -> bytes:
|
74
|
+
return hmac.new(key, _as_bytes(msg), hashlib.sha256).digest()
|
75
|
+
|
76
|
+
|
77
|
+
def _sha256_sign_hex(key: bytes, msg: str | bytes) -> str:
|
78
|
+
return hmac.new(key, _as_bytes(msg), hashlib.sha256).hexdigest()
|
79
|
+
|
80
|
+
|
81
|
+
_EMPTY_SHA256 = _sha256(b'')
|
82
|
+
|
83
|
+
_ISO8601 = '%Y%m%dT%H%M%SZ'
|
84
|
+
|
85
|
+
_SIGNED_HEADERS_BLACKLIST = frozenset([
|
86
|
+
'expect',
|
87
|
+
'user-agent',
|
88
|
+
'x-amzn-trace-id',
|
89
|
+
])
|
90
|
+
|
91
|
+
|
92
|
+
def _lower_case_http_map(d: HttpMap) -> HttpMap:
|
93
|
+
o: dict[str, list[str]] = {}
|
94
|
+
for k, vs in d.items():
|
95
|
+
o.setdefault(k.lower(), []).extend(vs)
|
96
|
+
return o
|
97
|
+
|
98
|
+
|
99
|
+
class V4AwsSigner:
|
100
|
+
def __init__(
|
101
|
+
self,
|
102
|
+
creds: Credentials,
|
103
|
+
region_name: str,
|
104
|
+
service_name: str,
|
105
|
+
) -> None:
|
106
|
+
super().__init__()
|
107
|
+
self._creds = creds
|
108
|
+
self._region_name = region_name
|
109
|
+
self._service_name = service_name
|
110
|
+
|
111
|
+
def _validate_request(self, req: Request) -> None:
|
112
|
+
check.non_empty_str(req.method)
|
113
|
+
check.equal(req.method.upper(), req.method)
|
114
|
+
for k, vs in req.headers.items():
|
115
|
+
check.equal(k.strip(), k)
|
116
|
+
for v in vs:
|
117
|
+
check.equal(v.strip(), v)
|
118
|
+
|
119
|
+
def sign(
|
120
|
+
self,
|
121
|
+
req: Request,
|
122
|
+
*,
|
123
|
+
sign_payload: bool = False,
|
124
|
+
utcnow: datetime.datetime | None = None,
|
125
|
+
) -> HttpMap:
|
126
|
+
self._validate_request(req)
|
127
|
+
|
128
|
+
#
|
129
|
+
|
130
|
+
if utcnow is None:
|
131
|
+
utcnow = datetime.datetime.utcnow() # noqa
|
132
|
+
req_dt = utcnow.strftime(_ISO8601)
|
133
|
+
|
134
|
+
#
|
135
|
+
|
136
|
+
parsed_url = urllib.parse.urlsplit(req.url)
|
137
|
+
canon_uri = parsed_url.path
|
138
|
+
canon_qs = parsed_url.query
|
139
|
+
|
140
|
+
#
|
141
|
+
|
142
|
+
headers_to_sign = {
|
143
|
+
k: v
|
144
|
+
for k, v in _lower_case_http_map(req.headers).items()
|
145
|
+
if k not in _SIGNED_HEADERS_BLACKLIST
|
146
|
+
}
|
147
|
+
|
148
|
+
if 'host' not in headers_to_sign:
|
149
|
+
headers_to_sign['host'] = [_host_from_url(req.url)]
|
150
|
+
|
151
|
+
headers_to_sign['x-amz-date'] = [req_dt]
|
152
|
+
|
153
|
+
hashed_payload = _sha256(req.payload) if req.payload else _EMPTY_SHA256
|
154
|
+
if sign_payload:
|
155
|
+
headers_to_sign['x-amz-content-sha256'] = [hashed_payload]
|
156
|
+
|
157
|
+
sorted_header_names = sorted(headers_to_sign)
|
158
|
+
canon_headers = ''.join([
|
159
|
+
':'.join((k, ','.join(headers_to_sign[k]))) + '\n'
|
160
|
+
for k in sorted_header_names
|
161
|
+
])
|
162
|
+
signed_headers = ';'.join(sorted_header_names)
|
163
|
+
|
164
|
+
#
|
165
|
+
|
166
|
+
canon_req = '\n'.join([
|
167
|
+
req.method,
|
168
|
+
canon_uri,
|
169
|
+
canon_qs,
|
170
|
+
canon_headers,
|
171
|
+
signed_headers,
|
172
|
+
hashed_payload,
|
173
|
+
])
|
174
|
+
|
175
|
+
#
|
176
|
+
|
177
|
+
algorithm = 'AWS4-HMAC-SHA256'
|
178
|
+
scope_parts = [
|
179
|
+
req_dt[:8],
|
180
|
+
self._region_name,
|
181
|
+
self._service_name,
|
182
|
+
'aws4_request',
|
183
|
+
]
|
184
|
+
scope = '/'.join(scope_parts)
|
185
|
+
hashed_canon_req = _sha256(canon_req)
|
186
|
+
string_to_sign = '\n'.join([
|
187
|
+
algorithm,
|
188
|
+
req_dt,
|
189
|
+
scope,
|
190
|
+
hashed_canon_req,
|
191
|
+
])
|
192
|
+
|
193
|
+
#
|
194
|
+
|
195
|
+
key = self._creds.secret_key
|
196
|
+
key_date = _sha256_sign(f'AWS4{key}'.encode('utf-8'), req_dt[:8]) # noqa
|
197
|
+
key_region = _sha256_sign(key_date, self._region_name)
|
198
|
+
key_service = _sha256_sign(key_region, self._service_name)
|
199
|
+
key_signing = _sha256_sign(key_service, 'aws4_request')
|
200
|
+
sig = _sha256_sign_hex(key_signing, string_to_sign)
|
201
|
+
|
202
|
+
#
|
203
|
+
|
204
|
+
cred_scope = '/'.join([
|
205
|
+
self._creds.access_key,
|
206
|
+
*scope_parts,
|
207
|
+
])
|
208
|
+
auth = f'{algorithm} ' + ', '.join([
|
209
|
+
f'Credential={cred_scope}',
|
210
|
+
f'SignedHeaders={signed_headers}',
|
211
|
+
f'Signature={sig}',
|
212
|
+
])
|
213
|
+
|
214
|
+
#
|
215
|
+
|
216
|
+
out = {
|
217
|
+
'Authorization': [auth],
|
218
|
+
'X-Amz-Date': [req_dt],
|
219
|
+
}
|
220
|
+
if sign_payload:
|
221
|
+
out['X-Amz-Content-SHA256'] = [hashed_payload]
|
222
|
+
return out
|
File without changes
|
File without changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev11
|
4
4
|
Summary: ominfra
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omlish==0.0.0.dev11
|
16
16
|
Provides-Extra: all
|
17
17
|
Requires-Dist: paramiko>=3.4; extra == "all"
|
18
18
|
Requires-Dist: asyncssh>=2.16; python_version < "3.13" and extra == "all"
|
@@ -13,6 +13,9 @@ ominfra.egg-info/requires.txt
|
|
13
13
|
ominfra.egg-info/top_level.txt
|
14
14
|
ominfra/bootstrap/__init__.py
|
15
15
|
ominfra/bootstrap/bootstrap.py
|
16
|
+
ominfra/clouds/__init__.py
|
17
|
+
ominfra/clouds/aws/__init__.py
|
18
|
+
ominfra/clouds/aws/auth.py
|
16
19
|
ominfra/deploy/__init__.py
|
17
20
|
ominfra/deploy/_executor.py
|
18
21
|
ominfra/deploy/configs.py
|
@@ -12,7 +12,7 @@ authors = [
|
|
12
12
|
urls = {source = 'https://github.com/wrmsr/omlish'}
|
13
13
|
license = {text = 'BSD-3-Clause'}
|
14
14
|
requires-python = '>=3.12'
|
15
|
-
version = '0.0.0.
|
15
|
+
version = '0.0.0.dev11'
|
16
16
|
classifiers = [
|
17
17
|
'License :: OSI Approved :: BSD License',
|
18
18
|
'Development Status :: 2 - Pre-Alpha',
|
@@ -22,17 +22,17 @@ classifiers = [
|
|
22
22
|
]
|
23
23
|
description = 'ominfra'
|
24
24
|
dependencies = [
|
25
|
-
'omlish == 0.0.0.
|
25
|
+
'omlish == 0.0.0.dev11',
|
26
26
|
]
|
27
27
|
|
28
28
|
[project.optional-dependencies]
|
29
29
|
all = [
|
30
30
|
'paramiko >= 3.4',
|
31
|
-
|
31
|
+
'asyncssh >= 2.16; python_version < "3.13"',
|
32
32
|
]
|
33
33
|
ssh = [
|
34
34
|
'paramiko >= 3.4',
|
35
|
-
|
35
|
+
'asyncssh >= 2.16; python_version < "3.13"',
|
36
36
|
]
|
37
37
|
|
38
38
|
[tool.setuptools]
|
ominfra-0.0.0.dev10/README.rst
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
*omlish*
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|