142 lines
4.7 KiB
Python
142 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from tests.conftest import csrf_headers
|
|
|
|
|
|
def put_credentials(
|
|
client: TestClient,
|
|
payload: dict[str, object],
|
|
) -> dict[str, Any]:
|
|
response = client.put(
|
|
"/api/v1/integrations",
|
|
json=payload,
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
return response.json()["data"]
|
|
|
|
|
|
def create_instance(
|
|
client: TestClient,
|
|
payload: dict[str, object],
|
|
) -> dict[str, Any]:
|
|
response = client.post(
|
|
"/api/v1/instances",
|
|
json=payload,
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert response.status_code == 201, response.text
|
|
return response.json()["data"]
|
|
|
|
|
|
def test_credentials_api_hides_secrets_and_replacing_pair_clears_session_token(
|
|
authenticated_client: TestClient,
|
|
integration_payload: dict[str, object],
|
|
) -> None:
|
|
client = authenticated_client
|
|
assert client.put("/api/v1/integrations", json=integration_payload).status_code == 403
|
|
|
|
initial = put_credentials(client, integration_payload)
|
|
assert initial["secrets_configured"] == {
|
|
"aws_access_key_id": True,
|
|
"aws_secret_access_key": True,
|
|
"aws_session_token": True,
|
|
"cloudflare_api_token": True,
|
|
}
|
|
assert "aws-secret-value" not in str(initial)
|
|
|
|
replacement = put_credentials(
|
|
client,
|
|
{
|
|
"use_default_aws_credentials": False,
|
|
"aws_access_key_id": "AKIA_REPLACEMENT_ACCESS_KEY",
|
|
"aws_secret_access_key": "replacement-secret-value",
|
|
"aws_session_token": None,
|
|
"cloudflare_api_token": None,
|
|
},
|
|
)
|
|
assert replacement["config_version"] == initial["config_version"] + 1
|
|
assert replacement["secrets_configured"]["aws_session_token"] is False
|
|
|
|
repository = client.app.state.container.integration_repository
|
|
secrets = repository.get_secrets()
|
|
assert secrets == {
|
|
"aws_access_key_id": "AKIA_REPLACEMENT_ACCESS_KEY",
|
|
"aws_secret_access_key": "replacement-secret-value",
|
|
"aws_session_token": None,
|
|
"cloudflare_api_token": "cloudflare-secret-value",
|
|
}
|
|
fetched = client.get("/api/v1/integrations")
|
|
assert fetched.status_code == 200
|
|
assert fetched.json()["data"] == replacement
|
|
assert not any(secret in fetched.text for secret in secrets.values() if secret)
|
|
|
|
|
|
def test_active_run_blocks_credentials_but_attention_allows_secret_only_audit(
|
|
authenticated_client: TestClient,
|
|
integration_payload: dict[str, object],
|
|
managed_instance_payload: dict[str, object],
|
|
monkeypatch,
|
|
) -> None:
|
|
client = authenticated_client
|
|
initial = put_credentials(client, integration_payload)
|
|
instance = create_instance(client, managed_instance_payload)
|
|
rotation_service = client.app.state.container.rotation_service
|
|
monkeypatch.setattr(rotation_service, "_spawn", lambda _run_id: None)
|
|
started = client.post(
|
|
f"/api/v1/instances/{instance['id']}/rotations",
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert started.status_code == 202
|
|
run_id = started.json()["data"]["id"]
|
|
|
|
blocked = client.put(
|
|
"/api/v1/integrations",
|
|
json={
|
|
"use_default_aws_credentials": False,
|
|
"cloudflare_api_token": "blocked-secret",
|
|
},
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert blocked.status_code == 409
|
|
assert blocked.json()["type"] == "about:blank#rotation_in_progress"
|
|
|
|
repository = client.app.state.container.rotation_repository
|
|
assert repository.acquire_lease(run_id, "test-worker") is True
|
|
repository.transition_run(run_id, status="running", expected_owner="test-worker")
|
|
item = repository.current_item(run_id)
|
|
assert item is not None
|
|
repository.fail_execution(
|
|
run_id,
|
|
item.id,
|
|
error_code="CLOUDFLARE_TOKEN_EXPIRED",
|
|
error_message="token expired",
|
|
outcome="needs_attention",
|
|
expected_owner="test-worker",
|
|
)
|
|
|
|
replacement_token = "recovery-cloudflare-secret"
|
|
recovered = client.put(
|
|
"/api/v1/integrations",
|
|
json={
|
|
"use_default_aws_credentials": False,
|
|
"cloudflare_api_token": replacement_token,
|
|
},
|
|
headers=csrf_headers(client),
|
|
)
|
|
assert recovered.status_code == 200, recovered.text
|
|
assert recovered.json()["data"]["config_version"] == initial["config_version"]
|
|
assert (
|
|
client.app.state.container.integration_repository.get_secret("cloudflare_api_token")
|
|
== replacement_token
|
|
)
|
|
|
|
events = repository.list_events(run_id)
|
|
assert events[-1]["message"] == "管理员已更新恢复凭据"
|
|
assert events[-1]["details"] == {"updated_credentials": ["cloudflare_api_token"]}
|
|
assert replacement_token not in str(events)
|