Implement AWS authentication
This commit is contained in:
parent
6ee5778878
commit
6b4eaa6448
@ -6,6 +6,7 @@ readme = "README.md"
|
||||
authors = []
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"cryptography>=44.0.2",
|
||||
"fastapi[standard]>=0.115.12",
|
||||
"libvirt-python>=11.1.0",
|
||||
"pydantic-xml>=2.14.3",
|
||||
|
45
src/openec2/cli/add_user.py
Normal file
45
src/openec2/cli/add_user.py
Normal file
@ -0,0 +1,45 @@
|
||||
import argparse
|
||||
import secrets
|
||||
import base64
|
||||
|
||||
from openec2.db import get_session
|
||||
from openec2.db.user import User
|
||||
|
||||
from sqlmodel import select
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--name", type=str, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
secret_access_key = base64.b64encode(
|
||||
secrets.token_bytes(64),
|
||||
).decode()
|
||||
|
||||
db = next(get_session())
|
||||
access_key = base64.b64encode(
|
||||
secrets.token_bytes(32),
|
||||
).decode()
|
||||
while True:
|
||||
if db.exec(select(User).where(User.access_key == access_key)).first() is None:
|
||||
break
|
||||
|
||||
access_key = base64.b64encode(
|
||||
secrets.token_bytes(32),
|
||||
).decode()
|
||||
|
||||
db.add(
|
||||
User(
|
||||
name=args.name,
|
||||
access_key=access_key,
|
||||
secret_access_key=secret_access_key,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
print(f"Access key: {access_key}")
|
||||
print(f"Secret access key: {secret_access_key}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -30,6 +30,7 @@ class _OpenEC2Config(BaseModel):
|
||||
libvirt: _OpenEC2LibvirtConfig
|
||||
database: _OpenEC2DatabaseConfig
|
||||
debug: bool
|
||||
insecure: bool
|
||||
|
||||
def _get_config() -> _OpenEC2Config:
|
||||
# TODO: Read from disk
|
||||
@ -50,6 +51,7 @@ def _get_config() -> _OpenEC2Config:
|
||||
connection="qemu:///system"
|
||||
),
|
||||
debug=True,
|
||||
insecure=False,
|
||||
database=_OpenEC2DatabaseConfig(
|
||||
url="sqlite:////home/alexander/openec2/db2.sqlite3",
|
||||
debug=True,
|
||||
|
11
src/openec2/db/user.py
Normal file
11
src/openec2/db/user.py
Normal file
@ -0,0 +1,11 @@
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
class User(SQLModel, table=True):
|
||||
id: int = Field(default=None, primary_key=True)
|
||||
|
||||
# Human-readable id
|
||||
name: str
|
||||
|
||||
# For request signing
|
||||
access_key: str
|
||||
secret_access_key: str
|
@ -1,7 +1,11 @@
|
||||
from urllib.parse import parse_qs
|
||||
from typing import cast
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Request, Response
|
||||
from sqlalchemy import select
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
from openec2.security.aws import AWSSignature
|
||||
from openec2.utils.text import multiline_yaml_response
|
||||
from openec2.config import OpenEC2Config
|
||||
from openec2.db import DatabaseDep, engine
|
||||
@ -28,8 +32,24 @@ def healthz():
|
||||
}
|
||||
|
||||
@app.get("/Action", response_model=None)
|
||||
def action(request: Request, config: OpenEC2Config, db: DatabaseDep):
|
||||
action = request.query_params["Action"]
|
||||
def action(request: Request, config: OpenEC2Config, db: DatabaseDep, _: AWSSignature):
|
||||
return run_action(request, config, db, cast(dict, request.query_params))
|
||||
|
||||
@app.post("/Action", response_model=None)
|
||||
async def test(request: Request, config: OpenEC2Config, db: DatabaseDep, _: AWSSignature):
|
||||
query_params = {
|
||||
key: value[0] for key, value in parse_qs((await request.body()).decode()).items()
|
||||
}
|
||||
return run_action(request, config, db, cast(dict, query_params))
|
||||
|
||||
def run_action(
|
||||
request: Request,
|
||||
config: OpenEC2Config,
|
||||
db: DatabaseDep,
|
||||
query_params: dict[str, str],
|
||||
):
|
||||
print(query_params)
|
||||
action = query_params["Action"]
|
||||
return {
|
||||
"ImportImage": import_image,
|
||||
"DescribeImages": describe_images,
|
||||
@ -39,7 +59,8 @@ def action(request: Request, config: OpenEC2Config, db: DatabaseDep):
|
||||
"StartInstances": start_instances,
|
||||
"StopInstances": stop_instances,
|
||||
"DeregisterImage": deregister_image,
|
||||
}[action](request.query_params, config, db)
|
||||
}[action](query_params, config, db)
|
||||
|
||||
|
||||
@app.get("/private/cloudinit/{instance_id}/{entry}")
|
||||
def cloud_init_data(instance_id: str, entry: str, db: DatabaseDep):
|
||||
|
157
src/openec2/security/aws.py
Normal file
157
src/openec2/security/aws.py
Normal file
@ -0,0 +1,157 @@
|
||||
from typing import Annotated, cast
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
from hashlib import sha256
|
||||
from urllib.parse import quote, parse_qs
|
||||
|
||||
from sqlmodel import select
|
||||
from fastapi import Request, HTTPException, Depends
|
||||
from fastapi.datastructures import QueryParams, URL, Headers
|
||||
from cryptography.hazmat.primitives import hashes, hmac
|
||||
|
||||
from openec2.config import ConfigSingleton
|
||||
from openec2.db import DatabaseDep
|
||||
from openec2.db.user import User
|
||||
|
||||
|
||||
def _hmac_sha256(key: bytes, payload: bytes) -> bytes:
|
||||
h = hmac.HMAC(key, hashes.SHA256())
|
||||
h.update(payload)
|
||||
return h.finalize()
|
||||
|
||||
@dataclass
|
||||
class AWSRequest:
|
||||
# The entire used URL
|
||||
url: URL
|
||||
|
||||
# Request method
|
||||
method: str
|
||||
|
||||
# Query params
|
||||
params: dict[str, str]
|
||||
|
||||
# Request headers
|
||||
headers: Headers
|
||||
|
||||
# The payload, if we used a POST/PUT
|
||||
payload: str | None
|
||||
|
||||
def sign(self, secret_access_key: str, region: str, product: str, credential_scope: str) -> str:
|
||||
dt = datetime.datetime.fromisoformat(self.headers["X-Amz-Date"])
|
||||
|
||||
canonical_query_string_keys = sorted(self.params.keys())
|
||||
canonical_query_string = "&".join([
|
||||
f"{key}={quote(self.params[key][0])}" for key in canonical_query_string_keys if key not in (
|
||||
"X-Amz-Signature",
|
||||
)
|
||||
])
|
||||
canonical_header_string_keys = sorted([
|
||||
name for name in self.headers.keys() if include_in_canonical_string(name)
|
||||
])
|
||||
canonical_header_string = "\n".join([
|
||||
f"{name.lower()}:{self.headers[name].strip()}" for name in canonical_header_string_keys
|
||||
]) + "\n"
|
||||
signed_headers = ";".join(canonical_header_string_keys)
|
||||
hashed_payload = sha256((self.payload or "").encode()).hexdigest()
|
||||
canonical_request = "\n".join([
|
||||
self.method.upper(),
|
||||
self.url.path or "/",
|
||||
#canonical_query_string,
|
||||
"",
|
||||
canonical_header_string,
|
||||
signed_headers,
|
||||
hashed_payload,
|
||||
])
|
||||
print("Canonical request")
|
||||
print(canonical_request)
|
||||
|
||||
hashed_canonical_request = sha256(canonical_request.encode()).hexdigest()
|
||||
date = dt.strftime("%Y%m%d")
|
||||
string_to_sign = "\n".join([
|
||||
"AWS4-HMAC-SHA256",
|
||||
dt.strftime("%Y%m%dT%H%M%SZ"),
|
||||
credential_scope,
|
||||
hashed_canonical_request,
|
||||
])
|
||||
|
||||
print("String to sign")
|
||||
print(string_to_sign)
|
||||
|
||||
date_key = _hmac_sha256(f"AWS4{secret_access_key}".encode(), date.encode())
|
||||
date_region_key = _hmac_sha256(date_key, region.encode())
|
||||
date_region_service_key = _hmac_sha256(date_region_key, product.encode())
|
||||
signing_key = _hmac_sha256(date_region_service_key, "aws4_request".encode())
|
||||
return _hmac_sha256(signing_key, string_to_sign.encode()).hex()
|
||||
|
||||
@dataclass
|
||||
class AWSAuthentication:
|
||||
x_amz_algorithm: str
|
||||
|
||||
x_amz_credential: str
|
||||
|
||||
x_amz_signature: str
|
||||
|
||||
def include_in_canonical_string(name: str) -> bool:
|
||||
lower = name.lower()
|
||||
return lower in ("host", "content-type") or lower.startswith("x-amz")
|
||||
|
||||
def get_authentication_info(request: Request) -> AWSAuthentication:
|
||||
if request.method == "POST":
|
||||
algorithm, rest = request.headers["Authorization"].split(" ", 1)
|
||||
auth = {}
|
||||
for part in rest.split(","):
|
||||
key, value = part.split("=", 1)
|
||||
auth[key.strip()] = value.strip()
|
||||
|
||||
return AWSAuthentication(
|
||||
x_amz_algorithm=algorithm,
|
||||
x_amz_credential=auth["Credential"],
|
||||
x_amz_signature=auth["Signature"],
|
||||
)
|
||||
|
||||
return AWSAuthentication(
|
||||
"", "", "",
|
||||
)
|
||||
|
||||
async def check_request_signature(request: Request, db: DatabaseDep):
|
||||
# Do not check if we don't care
|
||||
if ConfigSingleton.of().config.insecure:
|
||||
return
|
||||
|
||||
body = (await request.body()).decode()
|
||||
query_params = cast(dict, parse_qs(body)) if request.method == "POST" else cast(dict, request.query_params)
|
||||
|
||||
auth_info = get_authentication_info(request)
|
||||
|
||||
if auth_info.x_amz_algorithm != "AWS4-HMAC-SHA256":
|
||||
raise HTTPException(status_code=400, detail=f"Invalid signature algorithm: {x_amz_algorithm}")
|
||||
|
||||
x_amz_credential = auth_info.x_amz_credential
|
||||
|
||||
reversed_parts = x_amz_credential[::-1].split("/", 4)
|
||||
access_key, date, region, service, key = [s[::-1] for s in reversed_parts][::-1]
|
||||
user = db.exec(select(User).where(User.access_key == access_key)).first()
|
||||
if user is None:
|
||||
raise HTTPException(status_code=403)
|
||||
|
||||
x_amz_signature = auth_info.x_amz_signature
|
||||
signature = AWSRequest(
|
||||
url=request.url,
|
||||
method=request.method,
|
||||
params=query_params,
|
||||
headers=request.headers,
|
||||
payload=body,
|
||||
).sign(
|
||||
user.secret_access_key,
|
||||
region,
|
||||
service,
|
||||
"/".join([
|
||||
date, region, service, key
|
||||
]),
|
||||
)
|
||||
|
||||
print(x_amz_signature, signature)
|
||||
if x_amz_signature != signature:
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
AWSSignature = Annotated[None, Depends(check_request_signature)]
|
68
uv.lock
68
uv.lock
@ -33,6 +33,28 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pycparser" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.1"
|
||||
@ -76,6 +98,41 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.7.0"
|
||||
@ -307,6 +364,7 @@ name = "openec2"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "fastapi", extra = ["standard"] },
|
||||
{ name = "libvirt-python" },
|
||||
{ name = "pydantic-xml" },
|
||||
@ -317,6 +375,7 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "cryptography", specifier = ">=44.0.2" },
|
||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
|
||||
{ name = "libvirt-python", specifier = ">=11.1.0" },
|
||||
{ name = "pydantic-xml", specifier = ">=2.14.3" },
|
||||
@ -343,6 +402,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.0"
|
||||
|
Loading…
Reference in New Issue
Block a user