stackport 0.4.4__tar.gz → 0.4.5__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.
- {stackport-0.4.4/stackport.egg-info → stackport-0.4.5}/PKG-INFO +1 -1
- {stackport-0.4.4 → stackport-0.4.5}/backend/main.py +2 -1
- stackport-0.4.5/backend/routes/kms.py +163 -0
- {stackport-0.4.4 → stackport-0.4.5}/pyproject.toml +1 -1
- {stackport-0.4.4 → stackport-0.4.5/stackport.egg-info}/PKG-INFO +1 -1
- {stackport-0.4.4 → stackport-0.4.5}/stackport.egg-info/SOURCES.txt +28 -26
- stackport-0.4.5/tests/test_kms_routes.py +275 -0
- stackport-0.4.4/ui/dist/assets/CloudscapeAbout-BRbsDskR.js → stackport-0.4.5/ui/dist/assets/CloudscapeAbout-D8bVx2o1.js +1 -1
- stackport-0.4.4/ui/dist/assets/CloudscapeDashboard-j2f4Elz2.js → stackport-0.4.5/ui/dist/assets/CloudscapeDashboard-Cb3SVBox.js +1 -1
- stackport-0.4.4/ui/dist/assets/CloudscapeLearn-CVW3k3X0.js → stackport-0.4.5/ui/dist/assets/CloudscapeLearn-DhnltSEL.js +1 -1
- stackport-0.4.4/ui/dist/assets/CloudscapeResourceBrowser-CgWdgn20.js → stackport-0.4.5/ui/dist/assets/CloudscapeResourceBrowser-BNBaU_zZ.js +11 -11
- stackport-0.4.4/ui/dist/assets/CloudscapeSettings-CIXFvPKv.js → stackport-0.4.5/ui/dist/assets/CloudscapeSettings-BMXbMPTT.js +1 -1
- stackport-0.4.4/ui/dist/assets/CloudscapeShell-DNfhSg0O.js → stackport-0.4.5/ui/dist/assets/CloudscapeShell-Dd2hVOGq.js +1 -1
- stackport-0.4.4/ui/dist/assets/StateMachineGraph-DvF_kIOO.js → stackport-0.4.5/ui/dist/assets/StateMachineGraph-gL9KABj0.js +1 -1
- stackport-0.4.4/ui/dist/assets/index-BkSgVQ0X.js → stackport-0.4.5/ui/dist/assets/index-BBLXT2u8.js +1 -1
- stackport-0.4.4/ui/dist/assets/index-BLdRlH9d.js → stackport-0.4.5/ui/dist/assets/index-BFIBfRr-.js +13 -13
- stackport-0.4.4/ui/dist/assets/index-Ba-oOcd3.js → stackport-0.4.5/ui/dist/assets/index-Blob5IHY.js +1 -1
- stackport-0.4.4/ui/dist/assets/index-Dw6TGweb.js → stackport-0.4.5/ui/dist/assets/index-CN6POuiI.js +1 -1
- stackport-0.4.4/ui/dist/assets/index-BC7E-Fm4.js → stackport-0.4.5/ui/dist/assets/index-D8EPia5i.js +1 -1
- stackport-0.4.4/ui/dist/assets/index-C5z3cir-.js → stackport-0.4.5/ui/dist/assets/index-u1-TTPf4.js +1 -1
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/index.html +1 -1
- {stackport-0.4.4 → stackport-0.4.5}/LICENSE +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/MANIFEST.in +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/README.md +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/__init__.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/aws_client.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/cache.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/cli.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/config.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/endpoint_store.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/learn/__init__.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/learn/content.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/learn/hotspots.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/learn/trails/aws-first-steps.json +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/learn/verify.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/learn_store.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/__init__.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/common.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/dynamodb.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/ec2.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/endpoints.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/iam.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/lambda_svc.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/learn.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/logs.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/monitoring.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/rds.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/resources.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/s3.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/secretsmanager.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/sns.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/sqs.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/stats.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/stepfunctions.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/routes/tags.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/__init__.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/dynamodb.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/endpoints.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/lambda_svc.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/learn.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/logs.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/monitoring.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/s3.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/secretsmanager.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/sns.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/sqs.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/stepfunctions.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/schemas/tags.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/backend/websocket.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/setup.cfg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/stackport.egg-info/dependency_links.txt +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/stackport.egg-info/entry_points.txt +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/stackport.egg-info/requires.txt +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/stackport.egg-info/top_level.txt +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_cache.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_cli.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_client.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_config.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_create_resources.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_dynamodb_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_ec2_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_endpoint_store.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_endpoints.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_iam_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_lambda_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_learn_content.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_learn_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_learn_store.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_logs_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_logs_tail.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_monitoring_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_multi_endpoint.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_rds_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_readonly_middleware.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_registries.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_s3_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_s3_upload_limit_env.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_secretsmanager_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_sns_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_sqs_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_tags_routes.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/tests/test_websocket.py +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/CloudscapeResourceBrowser-CzwePG-V.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/CloudscapeShell-BXHVWjDE.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/index-DE--A2EE.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/index-DKIdXFX0.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/index-DNBdeKId.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/index-DoY0HscW.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/index-DrkkXAoG.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/index-Vf0UFiCT.css +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/stepfunctions-graph-B6nvU4OB.js +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/assets/utils-Cfmwk2UT.js +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/acm.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/apigateway.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/appsync.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/athena.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/cloudformation.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/cloudfront.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/cognito-idp.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/dynamodb.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/ec2.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/ecr.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/ecs.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/elasticache.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/elasticfilesystem.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/elasticloadbalancing.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/elasticmapreduce.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/events.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/firehose.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/glue.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/iam.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/kinesis.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/kms.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/lambda.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/logs.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/monitoring.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/rds.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/route53.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/s3.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/secretsmanager.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/ses.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/sns.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/sqs.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/ssm.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/stepfunctions.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/aws-icons/wafv2.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/favicon.png +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/favicon.svg +0 -0
- {stackport-0.4.4 → stackport-0.4.5}/ui/dist/stackport-logo.svg +0 -0
|
@@ -17,7 +17,7 @@ from botocore.exceptions import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
from backend.config import LOG_LEVEL, STACKPORT_ALLOW_WRITES, STACKPORT_LEARN, STACKPORT_PORT
|
|
20
|
-
from backend.routes import dynamodb, ec2, endpoints, iam, lambda_svc, learn, logs, monitoring, resources, rds, s3, secretsmanager, sns, sqs, stats, stepfunctions, tags
|
|
20
|
+
from backend.routes import dynamodb, ec2, endpoints, iam, lambda_svc, learn, logs, monitoring, resources, rds, s3, secretsmanager, sns, sqs, stats, stepfunctions, tags, kms
|
|
21
21
|
from backend.websocket import logs_tail_endpoint, probe_loop, websocket_endpoint
|
|
22
22
|
|
|
23
23
|
|
|
@@ -137,6 +137,7 @@ app.include_router(resources.router, prefix="/api")
|
|
|
137
137
|
app.include_router(rds.router, prefix="/api/rds", tags=["rds"])
|
|
138
138
|
app.include_router(monitoring.router, prefix="/api/monitoring", tags=["monitoring"])
|
|
139
139
|
app.include_router(sns.router, prefix="/api/sns", tags=["sns"])
|
|
140
|
+
app.include_router(kms.router, prefix="/api/kms", tags=["kms"])
|
|
140
141
|
if STACKPORT_LEARN:
|
|
141
142
|
app.include_router(learn.router, prefix="/api/learn", tags=["learn"])
|
|
142
143
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
2
|
+
from backend.routes.common import EndpointInfo, get_endpoint_info
|
|
3
|
+
from backend.aws_client import get_client
|
|
4
|
+
import json, logging
|
|
5
|
+
from botocore.exceptions import ClientError
|
|
6
|
+
|
|
7
|
+
router = APIRouter()
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get("/keys")
|
|
13
|
+
def list_keys(ep: EndpointInfo = Depends(get_endpoint_info)):
|
|
14
|
+
"""List all KMS keys with basic information"""
|
|
15
|
+
try:
|
|
16
|
+
client = get_client("kms", **ep.client_kwargs())
|
|
17
|
+
paginator = client.get_paginator("list_keys")
|
|
18
|
+
|
|
19
|
+
all_keys = []
|
|
20
|
+
|
|
21
|
+
for pages in paginator.paginate():
|
|
22
|
+
for key in pages["Keys"]:
|
|
23
|
+
key_id = key["KeyId"]
|
|
24
|
+
|
|
25
|
+
metadata = client.describe_key(KeyId=key_id)["KeyMetadata"]
|
|
26
|
+
|
|
27
|
+
all_keys.append(
|
|
28
|
+
{
|
|
29
|
+
"status": metadata["KeyState"],
|
|
30
|
+
"creationDate": metadata["CreationDate"],
|
|
31
|
+
"keySpec": metadata["KeySpec"],
|
|
32
|
+
"keyUsage": metadata["KeyUsage"],
|
|
33
|
+
"keyID": key_id,
|
|
34
|
+
"keyArn": metadata["Arn"],
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return {"keys": all_keys}
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.error("Failed to list keys with metadata %s", id, exc_info=True)
|
|
41
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.get("/keys/{id}")
|
|
45
|
+
def get_key_detail(id: str, ep: EndpointInfo = Depends(get_endpoint_info)):
|
|
46
|
+
"Get detailed information for a specific kms key."
|
|
47
|
+
try:
|
|
48
|
+
client = get_client("kms", **ep.client_kwargs())
|
|
49
|
+
metadata = client.describe_key(KeyId=id)["KeyMetadata"]
|
|
50
|
+
tags = client.list_resource_tags(KeyId=id)
|
|
51
|
+
rotation_status = client.get_key_rotation_status(KeyId=id)
|
|
52
|
+
return {
|
|
53
|
+
"status": metadata["KeyState"],
|
|
54
|
+
"expiresAt": metadata.get("ValidTo"),
|
|
55
|
+
"origin": metadata["Origin"],
|
|
56
|
+
"description": metadata.get("Description"),
|
|
57
|
+
"tags": tags["Tags"],
|
|
58
|
+
"rotationStatus": {
|
|
59
|
+
"keyRotationEnabled": rotation_status["KeyRotationEnabled"],
|
|
60
|
+
"rotationPeriodInDays": rotation_status.get("RotationPeriodInDays"),
|
|
61
|
+
"nextRotationDate": rotation_status.get("NextRotationDate"),
|
|
62
|
+
"onDemandRotationStartDate": rotation_status.get(
|
|
63
|
+
"OnDemandRotationStartDate"
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
except HTTPException:
|
|
68
|
+
raise
|
|
69
|
+
except ClientError as e:
|
|
70
|
+
error_code = e.response["Error"]["Code"]
|
|
71
|
+
if error_code == "NotFoundException":
|
|
72
|
+
raise HTTPException(status_code=404, detail=f"Key {id} not found")
|
|
73
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error("Failed to get details for %s", id, exc_info=True)
|
|
76
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@router.get("/keys/{id}/policy")
|
|
80
|
+
def get_key_policy(id: str, ep: EndpointInfo = Depends(get_endpoint_info)):
|
|
81
|
+
"Get key policy for a specific kms key"
|
|
82
|
+
try:
|
|
83
|
+
client = get_client("kms", **ep.client_kwargs())
|
|
84
|
+
policy = client.get_key_policy(KeyId=id)
|
|
85
|
+
policy = json.loads(policy["Policy"])
|
|
86
|
+
return policy
|
|
87
|
+
except HTTPException:
|
|
88
|
+
raise
|
|
89
|
+
except ClientError as e:
|
|
90
|
+
error_code = e.response["Error"]["Code"]
|
|
91
|
+
if error_code == "NotFoundException":
|
|
92
|
+
raise HTTPException(status_code=404, detail=f"Key {id} not found")
|
|
93
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error("Failed to get policy document for %s", id, exc_info=True)
|
|
96
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@router.get("/keys/{id}/grants")
|
|
100
|
+
def list_grants(id: str, ep: EndpointInfo = Depends(get_endpoint_info)):
|
|
101
|
+
"List grants for a specific key"
|
|
102
|
+
try:
|
|
103
|
+
client = get_client("kms", **ep.client_kwargs())
|
|
104
|
+
paginator = client.get_paginator("list_grants")
|
|
105
|
+
|
|
106
|
+
all_grants = []
|
|
107
|
+
for pages in paginator.paginate(KeyId=id):
|
|
108
|
+
for grant in pages["Grants"]:
|
|
109
|
+
all_grants.append(
|
|
110
|
+
{
|
|
111
|
+
"grantID": grant["GrantId"],
|
|
112
|
+
"grantee": grant["GranteePrincipal"],
|
|
113
|
+
"operations": grant["Operations"],
|
|
114
|
+
"creationDate": grant["CreationDate"],
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return all_grants
|
|
119
|
+
except HTTPException:
|
|
120
|
+
raise
|
|
121
|
+
except ClientError as e:
|
|
122
|
+
error_code = e.response["Error"]["Code"]
|
|
123
|
+
if error_code == "NotFoundException":
|
|
124
|
+
raise HTTPException(status_code=404, detail=f"Key {id} not found")
|
|
125
|
+
if error_code == "InvalidAction":
|
|
126
|
+
logger.warning(
|
|
127
|
+
"ListGrants is not supported by the configured KMS backend",
|
|
128
|
+
exc_info=True,
|
|
129
|
+
)
|
|
130
|
+
return []
|
|
131
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error("Failed to list grants for %s", id, exc_info=True)
|
|
134
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@router.get("/keys/{id}/aliases")
|
|
138
|
+
def get_aliases_detail(id: str, ep: EndpointInfo = Depends(get_endpoint_info)):
|
|
139
|
+
try:
|
|
140
|
+
client = get_client("kms", **ep.client_kwargs())
|
|
141
|
+
paginator = client.get_paginator("list_aliases")
|
|
142
|
+
|
|
143
|
+
all_aliases = []
|
|
144
|
+
for pages in paginator.paginate(KeyId=id):
|
|
145
|
+
for alias in pages["Aliases"]:
|
|
146
|
+
all_aliases.append(
|
|
147
|
+
{
|
|
148
|
+
"aliasName": alias["AliasName"],
|
|
149
|
+
"aliasArn": alias["AliasArn"],
|
|
150
|
+
"creationDate": alias.get("CreationDate"),
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
return all_aliases
|
|
154
|
+
except HTTPException:
|
|
155
|
+
raise
|
|
156
|
+
except ClientError as e:
|
|
157
|
+
error_code = e.response["Error"]["Code"]
|
|
158
|
+
if error_code == "NotFoundException":
|
|
159
|
+
raise HTTPException(status_code=404, detail=f"Key {id} not found")
|
|
160
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error("Failed to get aliases for %s", id, exc_info=True)
|
|
163
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -15,27 +15,27 @@ backend/../ui/dist/favicon.png
|
|
|
15
15
|
backend/../ui/dist/favicon.svg
|
|
16
16
|
backend/../ui/dist/index.html
|
|
17
17
|
backend/../ui/dist/stackport-logo.svg
|
|
18
|
-
backend/../ui/dist/assets/CloudscapeAbout-
|
|
19
|
-
backend/../ui/dist/assets/CloudscapeDashboard-
|
|
20
|
-
backend/../ui/dist/assets/CloudscapeLearn-
|
|
21
|
-
backend/../ui/dist/assets/CloudscapeResourceBrowser-
|
|
18
|
+
backend/../ui/dist/assets/CloudscapeAbout-D8bVx2o1.js
|
|
19
|
+
backend/../ui/dist/assets/CloudscapeDashboard-Cb3SVBox.js
|
|
20
|
+
backend/../ui/dist/assets/CloudscapeLearn-DhnltSEL.js
|
|
21
|
+
backend/../ui/dist/assets/CloudscapeResourceBrowser-BNBaU_zZ.js
|
|
22
22
|
backend/../ui/dist/assets/CloudscapeResourceBrowser-CzwePG-V.css
|
|
23
|
-
backend/../ui/dist/assets/CloudscapeSettings-
|
|
23
|
+
backend/../ui/dist/assets/CloudscapeSettings-BMXbMPTT.js
|
|
24
24
|
backend/../ui/dist/assets/CloudscapeShell-BXHVWjDE.css
|
|
25
|
-
backend/../ui/dist/assets/CloudscapeShell-
|
|
26
|
-
backend/../ui/dist/assets/StateMachineGraph-
|
|
27
|
-
backend/../ui/dist/assets/index-
|
|
28
|
-
backend/../ui/dist/assets/index-
|
|
29
|
-
backend/../ui/dist/assets/index-
|
|
30
|
-
backend/../ui/dist/assets/index-
|
|
31
|
-
backend/../ui/dist/assets/index-
|
|
25
|
+
backend/../ui/dist/assets/CloudscapeShell-Dd2hVOGq.js
|
|
26
|
+
backend/../ui/dist/assets/StateMachineGraph-gL9KABj0.js
|
|
27
|
+
backend/../ui/dist/assets/index-BBLXT2u8.js
|
|
28
|
+
backend/../ui/dist/assets/index-BFIBfRr-.js
|
|
29
|
+
backend/../ui/dist/assets/index-Blob5IHY.js
|
|
30
|
+
backend/../ui/dist/assets/index-CN6POuiI.js
|
|
31
|
+
backend/../ui/dist/assets/index-D8EPia5i.js
|
|
32
32
|
backend/../ui/dist/assets/index-DE--A2EE.css
|
|
33
33
|
backend/../ui/dist/assets/index-DKIdXFX0.css
|
|
34
34
|
backend/../ui/dist/assets/index-DNBdeKId.css
|
|
35
35
|
backend/../ui/dist/assets/index-DoY0HscW.css
|
|
36
36
|
backend/../ui/dist/assets/index-DrkkXAoG.css
|
|
37
|
-
backend/../ui/dist/assets/index-Dw6TGweb.js
|
|
38
37
|
backend/../ui/dist/assets/index-Vf0UFiCT.css
|
|
38
|
+
backend/../ui/dist/assets/index-u1-TTPf4.js
|
|
39
39
|
backend/../ui/dist/assets/stepfunctions-graph-B6nvU4OB.js
|
|
40
40
|
backend/../ui/dist/assets/utils-Cfmwk2UT.js
|
|
41
41
|
backend/../ui/dist/aws-icons/acm.svg
|
|
@@ -83,6 +83,7 @@ backend/routes/dynamodb.py
|
|
|
83
83
|
backend/routes/ec2.py
|
|
84
84
|
backend/routes/endpoints.py
|
|
85
85
|
backend/routes/iam.py
|
|
86
|
+
backend/routes/kms.py
|
|
86
87
|
backend/routes/lambda_svc.py
|
|
87
88
|
backend/routes/learn.py
|
|
88
89
|
backend/routes/logs.py
|
|
@@ -125,6 +126,7 @@ tests/test_ec2_routes.py
|
|
|
125
126
|
tests/test_endpoint_store.py
|
|
126
127
|
tests/test_endpoints.py
|
|
127
128
|
tests/test_iam_routes.py
|
|
129
|
+
tests/test_kms_routes.py
|
|
128
130
|
tests/test_lambda_routes.py
|
|
129
131
|
tests/test_learn_content.py
|
|
130
132
|
tests/test_learn_routes.py
|
|
@@ -148,27 +150,27 @@ ui/dist/favicon.png
|
|
|
148
150
|
ui/dist/favicon.svg
|
|
149
151
|
ui/dist/index.html
|
|
150
152
|
ui/dist/stackport-logo.svg
|
|
151
|
-
ui/dist/assets/CloudscapeAbout-
|
|
152
|
-
ui/dist/assets/CloudscapeDashboard-
|
|
153
|
-
ui/dist/assets/CloudscapeLearn-
|
|
154
|
-
ui/dist/assets/CloudscapeResourceBrowser-
|
|
153
|
+
ui/dist/assets/CloudscapeAbout-D8bVx2o1.js
|
|
154
|
+
ui/dist/assets/CloudscapeDashboard-Cb3SVBox.js
|
|
155
|
+
ui/dist/assets/CloudscapeLearn-DhnltSEL.js
|
|
156
|
+
ui/dist/assets/CloudscapeResourceBrowser-BNBaU_zZ.js
|
|
155
157
|
ui/dist/assets/CloudscapeResourceBrowser-CzwePG-V.css
|
|
156
|
-
ui/dist/assets/CloudscapeSettings-
|
|
158
|
+
ui/dist/assets/CloudscapeSettings-BMXbMPTT.js
|
|
157
159
|
ui/dist/assets/CloudscapeShell-BXHVWjDE.css
|
|
158
|
-
ui/dist/assets/CloudscapeShell-
|
|
159
|
-
ui/dist/assets/StateMachineGraph-
|
|
160
|
-
ui/dist/assets/index-
|
|
161
|
-
ui/dist/assets/index-
|
|
162
|
-
ui/dist/assets/index-
|
|
163
|
-
ui/dist/assets/index-
|
|
164
|
-
ui/dist/assets/index-
|
|
160
|
+
ui/dist/assets/CloudscapeShell-Dd2hVOGq.js
|
|
161
|
+
ui/dist/assets/StateMachineGraph-gL9KABj0.js
|
|
162
|
+
ui/dist/assets/index-BBLXT2u8.js
|
|
163
|
+
ui/dist/assets/index-BFIBfRr-.js
|
|
164
|
+
ui/dist/assets/index-Blob5IHY.js
|
|
165
|
+
ui/dist/assets/index-CN6POuiI.js
|
|
166
|
+
ui/dist/assets/index-D8EPia5i.js
|
|
165
167
|
ui/dist/assets/index-DE--A2EE.css
|
|
166
168
|
ui/dist/assets/index-DKIdXFX0.css
|
|
167
169
|
ui/dist/assets/index-DNBdeKId.css
|
|
168
170
|
ui/dist/assets/index-DoY0HscW.css
|
|
169
171
|
ui/dist/assets/index-DrkkXAoG.css
|
|
170
|
-
ui/dist/assets/index-Dw6TGweb.js
|
|
171
172
|
ui/dist/assets/index-Vf0UFiCT.css
|
|
173
|
+
ui/dist/assets/index-u1-TTPf4.js
|
|
172
174
|
ui/dist/assets/stepfunctions-graph-B6nvU4OB.js
|
|
173
175
|
ui/dist/assets/utils-Cfmwk2UT.js
|
|
174
176
|
ui/dist/aws-icons/acm.svg
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Tests for KMS API routes."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
os.environ.setdefault("AWS_ENDPOINT_URL", "http://localhost:4566")
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from unittest.mock import MagicMock, patch
|
|
9
|
+
|
|
10
|
+
from fastapi.testclient import TestClient
|
|
11
|
+
|
|
12
|
+
from backend.main import app
|
|
13
|
+
|
|
14
|
+
client = TestClient(app)
|
|
15
|
+
|
|
16
|
+
NOW = datetime(2024, 6, 1, tzinfo=timezone.utc)
|
|
17
|
+
from botocore.exceptions import ClientError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestListKeys:
|
|
21
|
+
@patch("backend.routes.kms.get_client")
|
|
22
|
+
def test_list_keys_empty(self, mock_get_client):
|
|
23
|
+
mock_paginator = MagicMock()
|
|
24
|
+
|
|
25
|
+
mock_paginator.paginate.return_value = [{"Keys": []}]
|
|
26
|
+
|
|
27
|
+
response = client.get("/api/kms/keys")
|
|
28
|
+
|
|
29
|
+
assert response.status_code == 200
|
|
30
|
+
data = response.json()
|
|
31
|
+
assert data["keys"] == []
|
|
32
|
+
|
|
33
|
+
@patch("backend.routes.kms.get_client")
|
|
34
|
+
def test_list_keys_with_data(self, mock_get_client):
|
|
35
|
+
mock_kms = MagicMock()
|
|
36
|
+
mock_get_client.return_value = mock_kms
|
|
37
|
+
|
|
38
|
+
mock_paginator = MagicMock()
|
|
39
|
+
|
|
40
|
+
mock_paginator.paginate.return_value = [
|
|
41
|
+
{
|
|
42
|
+
"Keys": [
|
|
43
|
+
{
|
|
44
|
+
"KeyId": "test-id",
|
|
45
|
+
"KeyArn": "arn:aws:kms:us-east-1:000000000000:key/test-id",
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
mock_kms.get_paginator.return_value = mock_paginator
|
|
52
|
+
|
|
53
|
+
mock_kms.describe_key.return_value = {
|
|
54
|
+
"KeyMetadata": {
|
|
55
|
+
"AWSAccountId": "000000000000",
|
|
56
|
+
"KeyId": "test-id",
|
|
57
|
+
"Arn": "arn:aws:kms:us-east-1:000000000000:key/test-id",
|
|
58
|
+
"CreationDate": NOW,
|
|
59
|
+
"Enabled": True,
|
|
60
|
+
"KeyUsage": "ENCRYPT_DECRYPT",
|
|
61
|
+
"KeyState": "Enabled",
|
|
62
|
+
"Origin": "AWS_KMS",
|
|
63
|
+
"KeyManager": "CUSTOMER",
|
|
64
|
+
"CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
|
|
65
|
+
"KeySpec": "SYMMETRIC_DEFAULT",
|
|
66
|
+
"EncryptionAlgorithms": ["SYMMETRIC_DEFAULT"],
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
response = client.get("/api/kms/keys")
|
|
71
|
+
|
|
72
|
+
assert response.status_code == 200
|
|
73
|
+
data = response.json()
|
|
74
|
+
assert data["keys"][0]["keyID"] == "test-id"
|
|
75
|
+
assert (
|
|
76
|
+
data["keys"][0]["keyArn"]
|
|
77
|
+
== "arn:aws:kms:us-east-1:000000000000:key/test-id"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TestGetKeyDetail:
|
|
82
|
+
@patch("backend.routes.kms.get_client")
|
|
83
|
+
def test_get_key_detail(self, mock_get_client):
|
|
84
|
+
mock_kms = MagicMock()
|
|
85
|
+
mock_get_client.return_value = mock_kms
|
|
86
|
+
mock_kms.describe_key.return_value = {
|
|
87
|
+
"KeyMetadata": {
|
|
88
|
+
"AWSAccountId": "000000000000",
|
|
89
|
+
"KeyId": "test-id",
|
|
90
|
+
"Arn": "arn:aws:kms:us-east-1:000000000000:key/test-id",
|
|
91
|
+
"CreationDate": NOW,
|
|
92
|
+
"Enabled": True,
|
|
93
|
+
"KeyUsage": "ENCRYPT_DECRYPT",
|
|
94
|
+
"KeyState": "Enabled",
|
|
95
|
+
"Origin": "AWS_KMS",
|
|
96
|
+
"KeyManager": "CUSTOMER",
|
|
97
|
+
"CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
|
|
98
|
+
"KeySpec": "SYMMETRIC_DEFAULT",
|
|
99
|
+
"EncryptionAlgorithms": ["SYMMETRIC_DEFAULT"],
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
mock_kms.list_resource_tags.return_value = {"Tags": []}
|
|
103
|
+
mock_kms.get_key_rotation_status.return_value = {"KeyRotationEnabled": False}
|
|
104
|
+
|
|
105
|
+
response = client.get("/api/kms/keys/test-id")
|
|
106
|
+
|
|
107
|
+
assert response.status_code == 200
|
|
108
|
+
data = response.json()
|
|
109
|
+
assert data["status"] == "Enabled"
|
|
110
|
+
assert data["tags"] == []
|
|
111
|
+
assert data["rotationStatus"].get("keyRotationEnabled") is False
|
|
112
|
+
|
|
113
|
+
@patch("backend.routes.kms.get_client")
|
|
114
|
+
def test_get_key_detail_not_found(self, mock_get_client):
|
|
115
|
+
mock_kms = MagicMock()
|
|
116
|
+
mock_get_client.return_value = mock_kms
|
|
117
|
+
mock_kms.describe_key.side_effect = ClientError(
|
|
118
|
+
error_response={
|
|
119
|
+
"Error": {"Code": "NotFoundException"}
|
|
120
|
+
},
|
|
121
|
+
operation_name="DescribeKey",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
response = client.get("/api/kms/keys/key-nonexistent")
|
|
125
|
+
print(response.text)
|
|
126
|
+
assert response.status_code == 404
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestGetKeyPolicy:
|
|
130
|
+
@patch("backend.routes.kms.get_client")
|
|
131
|
+
def test_get_key_policy(self, mock_get_client):
|
|
132
|
+
mock_kms = MagicMock()
|
|
133
|
+
mock_get_client.return_value = mock_kms
|
|
134
|
+
mock_kms.get_key_policy.return_value = {
|
|
135
|
+
"Policy": '{"Version":"2012-10-17","Statement":[{"Sid":"Enable IAM User Permissions","Effect":"Allow","Principal":{"AWS":"arn:aws:iam::000000000000:root"},"Action":"kms:*","Resource":"*"}]}',
|
|
136
|
+
"PolicyName": "default",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
response = client.get("/api/kms/keys/test-id/policy")
|
|
140
|
+
|
|
141
|
+
assert response.status_code == 200
|
|
142
|
+
data = response.json()
|
|
143
|
+
assert data["Version"] == "2012-10-17"
|
|
144
|
+
assert data["Statement"] == [
|
|
145
|
+
{
|
|
146
|
+
"Sid": "Enable IAM User Permissions",
|
|
147
|
+
"Effect": "Allow",
|
|
148
|
+
"Principal": {"AWS": "arn:aws:iam::000000000000:root"},
|
|
149
|
+
"Action": "kms:*",
|
|
150
|
+
"Resource": "*",
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
@patch("backend.routes.kms.get_client")
|
|
155
|
+
def test_get_key_policy_not_found(self, mock_get_client):
|
|
156
|
+
mock_kms = MagicMock()
|
|
157
|
+
mock_get_client.return_value = mock_kms
|
|
158
|
+
mock_kms.get_key_policy.side_effect = ClientError(
|
|
159
|
+
error_response={
|
|
160
|
+
"Error": {"Code": "NotFoundException"}
|
|
161
|
+
},
|
|
162
|
+
operation_name="GetKeyPolicy",
|
|
163
|
+
)
|
|
164
|
+
response = client.get("/api/kms/keys/test-id/policy")
|
|
165
|
+
assert response.status_code == 404
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TestListGrants:
|
|
169
|
+
@patch("backend.routes.kms.get_client")
|
|
170
|
+
def test_list_grants(self, mock_get_client):
|
|
171
|
+
mock_kms = MagicMock()
|
|
172
|
+
mock_get_client.return_value = mock_kms
|
|
173
|
+
mock_paginator = MagicMock()
|
|
174
|
+
mock_kms.get_paginator.return_value = mock_paginator
|
|
175
|
+
|
|
176
|
+
mock_paginator.paginate.return_value = [{
|
|
177
|
+
"Grants": [
|
|
178
|
+
{
|
|
179
|
+
"KeyId": "arn:aws:kms:us-east-1:000000000000:key/test-key",
|
|
180
|
+
"GrantId": "test-id",
|
|
181
|
+
"CreationDate": NOW,
|
|
182
|
+
"GranteePrincipal": "arn:aws:iam::000000000000:user/test-user",
|
|
183
|
+
"Operations": ["Encrypt", "Decrypt"],
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}]
|
|
187
|
+
|
|
188
|
+
response = client.get("/api/kms/keys/test-key/grants")
|
|
189
|
+
assert response.status_code == 200
|
|
190
|
+
data = response.json()
|
|
191
|
+
assert data[0]["grantID"] == "test-id"
|
|
192
|
+
assert data[0]["grantee"] == "arn:aws:iam::000000000000:user/test-user"
|
|
193
|
+
|
|
194
|
+
@patch("backend.routes.kms.get_client")
|
|
195
|
+
def test_list_grants_empty(self, mock_get_client):
|
|
196
|
+
mock_paginator = MagicMock()
|
|
197
|
+
|
|
198
|
+
mock_paginator.paginate.return_value = [{"Grants": []}]
|
|
199
|
+
|
|
200
|
+
response = client.get("/api/kms/keys/test-key/grants")
|
|
201
|
+
|
|
202
|
+
assert response.status_code == 200
|
|
203
|
+
|
|
204
|
+
@patch("backend.routes.kms.get_client")
|
|
205
|
+
def test_list_grants_not_found(self, mock_get_client):
|
|
206
|
+
mock_kms = MagicMock()
|
|
207
|
+
mock_paginator = MagicMock()
|
|
208
|
+
mock_kms.get_paginator.return_value = mock_paginator
|
|
209
|
+
mock_get_client.return_value = mock_kms
|
|
210
|
+
mock_paginator.paginate.side_effect = ClientError(
|
|
211
|
+
error_response={
|
|
212
|
+
"Error": {"Code": "NotFoundException"}
|
|
213
|
+
},
|
|
214
|
+
operation_name="ListGrants",
|
|
215
|
+
)
|
|
216
|
+
response = client.get("/api/kms/keys/test-key/grants")
|
|
217
|
+
|
|
218
|
+
assert response.status_code == 404
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TestGetAliasesDetail:
|
|
222
|
+
@patch("backend.routes.kms.get_client")
|
|
223
|
+
def test_get_aliases_detail(self, mock_get_client):
|
|
224
|
+
mock_paginator = MagicMock()
|
|
225
|
+
|
|
226
|
+
mock_kms = MagicMock()
|
|
227
|
+
mock_get_client.return_value = mock_kms
|
|
228
|
+
|
|
229
|
+
mock_kms.get_paginator.return_value = mock_paginator
|
|
230
|
+
|
|
231
|
+
mock_paginator.paginate.return_value = [{
|
|
232
|
+
"Aliases": [
|
|
233
|
+
{
|
|
234
|
+
"AliasName": "alias/test-alias",
|
|
235
|
+
"AliasArn": "arn:aws:kms:us-east-1:000000000000:alias/test-alias",
|
|
236
|
+
"TargetKeyId": "test-key",
|
|
237
|
+
"CreationDate": NOW,
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
}]
|
|
241
|
+
|
|
242
|
+
response = client.get("/api/kms/keys/test-key/aliases")
|
|
243
|
+
|
|
244
|
+
assert response.status_code == 200
|
|
245
|
+
data = response.json()
|
|
246
|
+
assert data[0]["aliasName"] == "alias/test-alias"
|
|
247
|
+
assert (
|
|
248
|
+
data[0]["aliasArn"] == "arn:aws:kms:us-east-1:000000000000:alias/test-alias"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
@patch("backend.routes.kms.get_client")
|
|
252
|
+
def test_get_aliases_detail_empty(self, mock_get_client):
|
|
253
|
+
mock_paginator = MagicMock()
|
|
254
|
+
|
|
255
|
+
mock_paginator.paginate.return_value = [{"Aliases": []}]
|
|
256
|
+
|
|
257
|
+
response = client.get("/api/kms/keys/test-key/aliases")
|
|
258
|
+
|
|
259
|
+
assert response.status_code == 200
|
|
260
|
+
|
|
261
|
+
@patch("backend.routes.kms.get_client")
|
|
262
|
+
def test_get_aliases_not_found(self, mock_get_client):
|
|
263
|
+
mock_kms = MagicMock()
|
|
264
|
+
mock_paginator = MagicMock()
|
|
265
|
+
mock_kms.get_paginator.return_value = mock_paginator
|
|
266
|
+
mock_get_client.return_value = mock_kms
|
|
267
|
+
mock_paginator.paginate.side_effect = ClientError(
|
|
268
|
+
error_response={
|
|
269
|
+
"Error": {"Code": "NotFoundException"}
|
|
270
|
+
},
|
|
271
|
+
operation_name="ListAliases",
|
|
272
|
+
)
|
|
273
|
+
response = client.get("/api/kms/keys/test-key/aliases")
|
|
274
|
+
|
|
275
|
+
assert response.status_code == 404
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as j,r as m,
|
|
1
|
+
import{b as j,r as m,e1 as f,u as y,j as e,S as o,B as p,A as b,e as i,d as u,L as v}from"./index-BFIBfRr-.js";import{C as S,H as a,K as d,B as h}from"./CloudscapeShell-Dd2hVOGq.js";import{C}from"./index-u1-TTPf4.js";import{f as g}from"./utils-Cfmwk2UT.js";const k=[{label:"GitHub Repository",url:"https://github.com/DaviReisVieira/stackport"},{label:"Report an Issue",url:"https://github.com/DaviReisVieira/stackport/issues"}];function r(t,n){return e.jsxs("div",{children:[e.jsx(u,{variant:"awsui-key-label",children:t}),e.jsx(u,{fontSize:"body-s",children:n})]},t)}function z(){const{activeEndpoint:t}=j(),n=m.useCallback(()=>f(t),[t]),{data:s,error:c,refresh:x}=y(n,1e4);return e.jsx(S,{activeHref:"/about",children:e.jsxs(o,{size:"l",children:[e.jsx(a,{variant:"h1",description:"System information and project details",actions:e.jsx(p,{iconName:"refresh",onClick:()=>x(),ariaLabel:"Refresh system info"}),children:"About"}),c&&!s&&e.jsx(b,{type:"error",header:"Failed to load system info",action:e.jsx(p,{onClick:()=>x(),children:"Retry"}),children:c}),!s&&!c&&e.jsx(i,{type:"loading",children:"Loading system info"}),s&&e.jsxs(C,{columns:3,children:[e.jsx(d,{header:e.jsx(a,{variant:"h3",children:"Project"}),children:e.jsxs(o,{size:"s",children:[r("Name","StackPort"),r("Version",e.jsx(h,{color:"blue",children:s.version})),r("License","MIT"),e.jsx(u,{color:"text-body-secondary",fontSize:"body-s",children:"Universal AWS resource browser for local emulators"})]})}),e.jsx(d,{header:e.jsx(a,{variant:"h3",children:"Connection"}),children:e.jsxs(o,{size:"s",children:[r("Mode",s.connection_type==="local"?e.jsx(h,{color:"grey",children:"Local Emulator"}):e.jsx(h,{color:"severity-medium",children:"Real AWS"})),r("Endpoint",s.endpoint_url??"AWS (default)"),r("Region",s.region),r("Services",String(s.services_count)),r("Writes",s.writes_enabled?e.jsx(i,{type:"success",children:"Enabled"}):e.jsx(i,{type:"stopped",children:"Disabled"})),r("Uptime",g(s.uptime_seconds)),r("Status",s.status==="ok"?e.jsx(i,{type:"success",children:"ok"}):e.jsx(i,{type:"error",children:s.status}))]})}),e.jsx(d,{header:e.jsx(a,{variant:"h3",children:"Links"}),children:e.jsx(o,{size:"s",children:k.map(l=>e.jsx(v,{href:l.url,external:!0,children:l.label},l.url))})})]})]})})}export{z as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a,u as L,a as W,b as O,c as A,f as D,j as t,S as $,A as E,B as R,d,e as F,L as M}from"./index-
|
|
1
|
+
import{r as a,u as L,a as W,b as O,c as A,f as D,j as t,S as $,A as E,B as R,d,e as F,L as M}from"./index-BFIBfRr-.js";import{u as P,a as H,H as B,C as U,B as _,g as V}from"./CloudscapeShell-Dd2hVOGq.js";import{S as J,C as G,T as K}from"./index-BBLXT2u8.js";import{C as q}from"./index-u1-TTPf4.js";import{C as Q}from"./index-Blob5IHY.js";import{T as X}from"./index-D8EPia5i.js";import{f as Y}from"./utils-Cfmwk2UT.js";function Z({fallbackFetcher:n,fallbackInterval:r=5e3,messageType:i,endpoint:x}){const[s,g]=a.useState(null),[y,j]=a.useState(!1),[p,C]=a.useState(!1),h=a.useRef(null),c=a.useRef(0),b=a.useRef(null),l=a.useRef(),k=3;a.useEffect(()=>{let v=!1;c.current=0;function S(){var o;if(v||((o=h.current)==null?void 0:o.readyState)===WebSocket.OPEN)return;const m=window.location.protocol==="https:"?"wss:":"ws:",e=new WebSocket(`${m}//${window.location.host}/ws`);h.current=e,e.onopen=()=>{if(v){e.close();return}j(!0),c.current=0,e.send(JSON.stringify({type:"subscribe",services:["all"],endpoint:x??void 0}))},e.onmessage=w=>{try{const f=JSON.parse(w.data);f.type===i&&f.data&&g(f.data)}catch{}},e.onclose=()=>{if(j(!1),h.current=null,!v)if(c.current++,c.current>=k)C(!0);else{const w=Math.min(1e3*Math.pow(2,c.current),3e4);b.current=setTimeout(()=>{var f;return(f=l.current)==null?void 0:f.call(l)},w)}},e.onerror=()=>{e.close()}}return l.current=S,S(),()=>{var m;v=!0,b.current&&clearTimeout(b.current),(m=h.current)==null||m.close(),g(null),C(!1)}},[i,x]);const u=L(n,p?r:void 0),T=s??u.data,N=!T&&u.loading;return{data:T,loading:N,error:p?u.error:null,connected:y,refresh:u.refresh}}function ee(){return localStorage.getItem("stackport:view-mode")==="list"?"list":"grid"}function z(n,r){const i=V(n.name);return t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(i,{className:"h-5 w-5 flex-shrink-0"}),t.jsx(M,{fontSize:"heading-m",href:`/resources/${n.name}`,onFollow:x=>{x.preventDefault(),r(n.name)},children:n.name})]})}function I(n){return n.total===0?t.jsx(d,{color:"text-status-inactive",children:"No resources"}):t.jsx($,{direction:"horizontal",size:"xs",children:Object.entries(n.resources).filter(([,r])=>r>0).map(([r,i])=>t.jsxs(_,{color:"grey",children:[r,": ",i]},r))})}function ce(){const n=W(),{activeEndpoint:r}=O(),{data:i}=A(),x=a.useCallback(()=>D(r),[r]),{data:s,loading:g,error:y,connected:j,refresh:p}=Z({fallbackFetcher:x,fallbackInterval:5e3,messageType:"stats",endpoint:r}),{toggleFavorite:C,isFavorite:h}=P(),[c,b]=a.useState(""),[l,k]=a.useState(ee);a.useEffect(()=>{localStorage.setItem("stackport:view-mode",l)},[l]),H([{key:"r",handler:()=>p()},{key:"v",handler:()=>k(e=>e==="grid"?"list":"grid")}]);const u=a.useMemo(()=>s?Object.entries(s.services).map(([e,o])=>({name:e,status:o.status,resources:o.resources,total:Object.values(o.resources).reduce((w,f)=>w+f,0),favorite:h(e)})).filter(e=>e.name.toLowerCase().includes(c.toLowerCase())).sort((e,o)=>Number(o.favorite)-Number(e.favorite)||o.total-e.total||e.name.localeCompare(o.name)):[],[s,c,h]),T=s?Object.values(s.services).filter(e=>e.status==="available").length:0,N=a.useCallback(e=>n(`/resources/${e}`),[n]),v=e=>t.jsx(R,{variant:"inline-icon",iconName:e.favorite?"star-filled":"star",ariaLabel:e.favorite?`Remove ${e.name} from favorites`:`Add ${e.name} to favorites`,onClick:()=>C(e.name)}),S=t.jsx(B,{variant:"h2",counter:s?`(${u.length})`:void 0,actions:t.jsx(J,{selectedId:l,onChange:({detail:e})=>k(e.selectedId),label:"View mode",options:[{id:"grid",text:"Grid"},{id:"list",text:"List"}]}),children:"Services"}),m=t.jsx(K,{filteringText:c,filteringPlaceholder:"Find a service",onChange:({detail:e})=>b(e.filteringText)});return t.jsx(U,{activeHref:"/",children:t.jsx(Q,{header:t.jsx(B,{variant:"h1",description:r?`Connected to ${r}`:i!=null&&i.endpoint_url?`Connected to ${i.endpoint_url}`:"Using the default endpoint",actions:t.jsxs($,{direction:"horizontal",size:"xs",children:[t.jsx(F,{type:j?"success":"in-progress",children:j?"Live":"Polling"}),t.jsx(R,{iconName:"refresh",onClick:()=>p(),loading:g,children:"Refresh"})]}),children:"Dashboard"}),children:t.jsxs($,{size:"l",children:[!s&&y&&t.jsx(E,{type:"error",header:"Could not reach the endpoint",action:t.jsx(R,{onClick:()=>p(),children:"Retry"}),children:y}),t.jsxs(q,{columns:3,variant:"text-grid",children:[t.jsxs("div",{children:[t.jsx(d,{variant:"awsui-key-label",children:"Services available"}),t.jsx(d,{fontSize:"display-l",fontWeight:"bold",children:s?`${T}/${Object.keys(s.services).length}`:"—"})]}),t.jsxs("div",{children:[t.jsx(d,{variant:"awsui-key-label",children:"Total resources"}),t.jsx(d,{fontSize:"display-l",fontWeight:"bold",children:s?s.total_resources:"—"})]}),t.jsxs("div",{children:[t.jsx(d,{variant:"awsui-key-label",children:"Uptime"}),t.jsx(d,{fontSize:"display-l",fontWeight:"bold",children:s?Y(s.uptime_seconds):"—"})]})]}),l==="grid"?t.jsx(G,{items:u,loading:g&&!s,loadingText:"Probing services",trackBy:"name",cardsPerRow:[{cards:1},{minWidth:500,cards:2},{minWidth:900,cards:3}],header:S,filter:m,cardDefinition:{header:e=>t.jsxs("div",{className:"flex items-center justify-between gap-2",children:[z(e,N),v(e)]}),sections:[{id:"status",content:e=>t.jsx(F,{type:e.status==="available"?"success":"error",children:e.status==="available"?"Available":"Unavailable"})},{id:"resources",header:"Resources",content:e=>I(e)}]},empty:t.jsx(d,{textAlign:"center",children:"No services match the filter"})}):t.jsx(X,{items:u,loading:g&&!s,loadingText:"Probing services",trackBy:"name",header:S,filter:m,variant:"container",columnDefinitions:[{id:"favorite",header:"",width:60,cell:e=>v(e)},{id:"service",header:"Service",cell:e=>z(e,N),sortingField:"name"},{id:"status",header:"Status",cell:e=>t.jsx(F,{type:e.status==="available"?"success":"error",children:e.status==="available"?"Available":"Unavailable"})},{id:"resources",header:"Resources",cell:e=>I(e)},{id:"total",header:"Total",width:90,cell:e=>e.total}],empty:t.jsx(d,{textAlign:"center",children:"No services match the filter"})})]})})})}export{ce as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{e2 as S,c as v,r as u,j as e,S as c,A as x,d as a,B as d}from"./index-BFIBfRr-.js";import{C as L}from"./index-u1-TTPf4.js";import{C as b,ab as k,K as w,U as R,H as p,ac as A,M as z}from"./CloudscapeShell-Dd2hVOGq.js";import{C as B}from"./index-Blob5IHY.js";function T(){const{enabled:j,loading:f,trail:s,completed:y,resetTrail:g}=S(),{data:n}=v(),[m,r]=u.useState(!1),[C,h]=u.useState(!1),o=(s==null?void 0:s.lessons.reduce((t,l)=>t+l.steps.length,0))??0,i=Object.values(y).reduce((t,l)=>t+l.length,0);return e.jsxs(b,{activeHref:"/learn",children:[e.jsx(B,{header:e.jsx(p,{variant:"h1",description:"Short hands-on lessons against your own emulator. You do the work in the console or the CLI, and StackPort checks the real resource state, so a step only turns green when the thing genuinely exists.",actions:i>0&&e.jsx(d,{onClick:()=>r(!0),"data-testid":"learn-reset",children:"Reset progress"}),children:"Learn"}),children:e.jsxs(c,{size:"l",children:[(n==null?void 0:n.connection_type)==="aws"&&e.jsx(x,{type:"warning",header:"Lessons need a local emulator",children:"You are connected to real AWS. Lessons create and delete resources, so they stay disabled until you switch to a local endpoint."}),!j&&e.jsxs(x,{type:"info",header:"Learn is turned off",children:["Start StackPort with ",e.jsx(a,{variant:"code",children:"STACKPORT_LEARN=true"})," to use the lessons."]}),f&&!s&&e.jsx(a,{textAlign:"center",padding:"xxl",children:e.jsx(k,{size:"large"})}),s&&e.jsxs(e.Fragment,{children:[e.jsx(w,{header:e.jsx(p,{variant:"h2",children:s.title}),children:e.jsxs(L,{columns:2,variant:"text-grid",children:[e.jsx(a,{variant:"p",color:"text-body-secondary",children:s.description}),e.jsx(R,{value:o>0?i/o*100:0,additionalInfo:`${i} of ${o} steps done`,label:"Your progress"})]})}),e.jsx(c,{size:"l",children:s.lessons.map(t=>e.jsx(A,{lesson:t},t.id))}),e.jsx(a,{variant:"small",color:"text-body-secondary",children:"More lessons are on the way: queues, fan-out, logs and metrics, and a table you query rather than scan."})]})]})}),e.jsx(z,{visible:m,onDismiss:()=>r(!1),header:"Reset your progress?",footer:e.jsx(a,{float:"right",children:e.jsxs(c,{size:"xs",direction:"horizontal",children:[e.jsx(d,{variant:"link",onClick:()=>r(!1),children:"Cancel"}),e.jsx(d,{variant:"primary",loading:C,onClick:async()=>{h(!0);try{await g(),r(!1)}finally{h(!1)}},"data-testid":"learn-reset-confirm",children:"Reset progress"})]})}),children:"Every step goes back to not done, and the lesson picks fresh resource names. Nothing you created in your emulator is deleted."})]})}export{T as default};
|