laniakea-api-server 0.2.4__tar.gz → 0.2.7__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.
Files changed (24) hide show
  1. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/PKG-INFO +1 -1
  2. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/auth.py +21 -32
  3. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/config.py +5 -1
  4. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/queue.py +36 -0
  5. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/agent.py +6 -2
  6. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/credentials.py +22 -0
  7. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/pyproject.toml +1 -1
  8. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/.github/workflows/publish.yml +0 -0
  9. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/.gitignore +0 -0
  10. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/LICENSE +0 -0
  11. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/README.md +0 -0
  12. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/__init__.py +0 -0
  13. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/cli.py +0 -0
  14. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/credential_parser.py +0 -0
  15. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/database.py +0 -0
  16. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/installer.py +0 -0
  17. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/main.py +0 -0
  18. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/models.py +0 -0
  19. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/.health.py.swp +0 -0
  20. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/__init__.py +0 -0
  21. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/agents.py +0 -0
  22. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/deployments.py +0 -0
  23. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/health.py +0 -0
  24. {laniakea_api_server-0.2.4 → laniakea_api_server-0.2.7}/laniakea_api/routers/tfstate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: laniakea-api-server
3
- Version: 0.2.4
3
+ Version: 0.2.7
4
4
  Summary: Laniakea Queue API — OIDC-authenticated gateway for cloud deployment orchestration
5
5
  Project-URL: Homepage, https://github.com/laniakea/laniakea-api
6
6
  Project-URL: Issues, https://github.com/laniakea/laniakea-api/issues
@@ -10,45 +10,34 @@ import httpx
10
10
  import jwt
11
11
  from fastapi import Depends, HTTPException, status
12
12
  from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
13
- from laniakea_api.config import (
14
- SECRET_KEY, ALGORITHM, SESSION_TTL_MINUTES,
15
- OIDC_DISCOVERY_URL, AGENT_MASTER_PASSWORD,
16
- )
13
+ from laniakea_api.config import (SECRET_KEY, ALGORITHM, SESSION_TTL_MINUTES,
14
+ OIDC_DISCOVERY_URLS, AGENT_MASTER_PASSWORD)
15
+
17
16
 
18
17
  # Initialize security OAuth2 Bearer Token
19
18
  _bearer = HTTPBearer()
20
19
 
21
20
  async def fetch_userinfo(oidc_token: str) -> dict:
22
21
  """
23
- Takes an authentication token provided by a user and asks an external identity server
24
- (the OIDC Provider) who that user actually is, retriving user info
22
+ Validate the token against every trusted issuer, first match wins.
23
+ Supports federated logins (IAM ReCaS + Keycloak).
25
24
  """
26
- try:
27
- # asynchronous HTTP to avoid waste of server resources
28
- async with httpx.AsyncClient(timeout=10) as client:
29
- # OIDC discovery
30
- discovery = await client.get(OIDC_DISCOVERY_URL)
31
- discovery.raise_for_status() # status 200: OK
32
- userinfo_url = discovery.json()["userinfo_endpoint"] # now final url is known
33
- # USER info request
34
- resp = await client.get(
35
- userinfo_url,
36
- headers={"Authorization": f"Bearer {oidc_token}"},
37
- )
38
- if resp.status_code != 200:
39
- raise HTTPException(
40
- status_code=status.HTTP_401_UNAUTHORIZED,
41
- detail=f"OIDC userinfo returned {resp.status_code}",
42
- )
43
- return resp.json()
44
- except HTTPException:
45
- raise
46
- except Exception as exc:
47
- raise HTTPException(
48
- status_code=status.HTTP_401_UNAUTHORIZED,
49
- detail=f"OIDC validation failed: {exc}",
50
- )
51
-
25
+ last_status = None
26
+ async with httpx.AsyncClient(timeout=10) as client:
27
+ for discovery_url in OIDC_DISCOVERY_URLS:
28
+ try:
29
+ discovery = await client.get(discovery_url)
30
+ discovery.raise_for_status()
31
+ userinfo_url = discovery.json()["userinfo_endpoint"]
32
+ resp = await client.get(userinfo_url,
33
+ headers={"Authorization": f"Bearer {oidc_token}"})
34
+ if resp.status_code == 200:
35
+ return resp.json()
36
+ last_status = resp.status_code
37
+ except Exception:
38
+ continue
39
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
40
+ detail=f"Token rejected by all trusted issuers (last: {last_status})")
52
41
 
53
42
  def create_session_token(user_info: dict) -> tuple:
54
43
  """
@@ -9,7 +9,8 @@ import os
9
9
  SECRET_KEY = os.getenv("SECRET_KEY", "")
10
10
  ALGORITHM = "HS256"
11
11
  SESSION_TTL_MINUTES = int(os.getenv("SESSION_TTL_MINUTES", "60")) # NOTE: maybe longer
12
- OIDC_DISCOVERY_URL = os.getenv("OIDC_DISCOVERY_URL", "")
12
+ #OIDC_DISCOVERY_URL = os.getenv("OIDC_DISCOVERY_URL", "")
13
+ OIDC_DISCOVERY_URLS = [u.strip() for u in os.getenv("OIDC_DISCOVERY_URL", "").split(",") if u.strip()]
13
14
 
14
15
  # Agent pool password
15
16
  # to revoke ALL agents: change this value and restart API + all agents.
@@ -49,5 +50,8 @@ VALID_STATUSES = {
49
50
  "CREATE_FAILED",
50
51
  "UPDATE_IN_PROGRESS",
51
52
  "UPDATE_FAILED",
53
+ "DELETE_IN_PROGRESS",
54
+ "DELETE_COMPLETE",
55
+ "DELETE_FAILED",
52
56
  }
53
57
 
@@ -113,3 +113,39 @@ def check_vault() -> str:
113
113
  except Exception as exc:
114
114
  return f"error: {exc}"
115
115
 
116
+ #per-cloud credentials: secret/<sub>/service_creds/<name>
117
+
118
+ def _sc_path(user_sub: str, name: str = "") -> str:
119
+ base = f"{user_sub}/service_creds"
120
+ return f"{base}/{name}" if name else base
121
+
122
+ def vault_list_service_creds(user_sub: str) -> list:
123
+ client = get_vault_client()
124
+ try:
125
+ resp = client.secrets.kv.v2.list_secrets(path=_sc_path(user_sub), mount_point=VAULT_MOUNT)
126
+ names = [k.rstrip("/") for k in resp["data"]["keys"]]
127
+ except Exception:
128
+ return []
129
+ out = []
130
+ for n in names:
131
+ data = vault_read_service_creds(user_sub, n)
132
+ out.append({"name": n, "service_type": data.get("service_type", "openstack")})
133
+ return out
134
+
135
+ def vault_read_service_creds(user_sub: str, name: str) -> dict:
136
+ client = get_vault_client()
137
+ try:
138
+ resp = client.secrets.kv.v2.read_secret_version(path=_sc_path(user_sub, name), mount_point=VAULT_MOUNT)
139
+ return resp["data"]["data"] or {}
140
+ except Exception:
141
+ return {}
142
+
143
+ def vault_write_service_creds(user_sub: str, name: str, data: dict) -> None:
144
+ client = get_vault_client()
145
+ client.secrets.kv.v2.create_or_update_secret(
146
+ path=_sc_path(user_sub, name), secret=data, mount_point=VAULT_MOUNT)
147
+
148
+ def vault_delete_service_creds(user_sub: str, name: str) -> None:
149
+ client = get_vault_client()
150
+ client.secrets.kv.v2.delete_metadata_and_all_versions(
151
+ path=_sc_path(user_sub, name), mount_point=VAULT_MOUNT)
@@ -23,9 +23,13 @@ def _validate_transition(current: str, new_status: str, uuid: str) -> None:
23
23
  "QUEUED": {"CREATE_IN_PROGRESS", "UPDATE_IN_PROGRESS"},
24
24
  "CREATE_IN_PROGRESS": {"CREATE_COMPLETE", "CREATE_FAILED", "QUEUED"},
25
25
  "UPDATE_IN_PROGRESS": {"UPDATE_FAILED"},
26
- "CREATE_COMPLETE": set(),
26
+ "CREATE_COMPLETE": {"DELETE_IN_PROGRESS"},
27
+ "DELETE_IN_PROGRESS": {"DELETE_COMPLETE", "DELETE_FAILED"},
28
+ "DELETE_FAILED": {"DELETE_IN_PROGRESS"},
27
29
  "CREATE_FAILED": set(),
28
- "UPDATE_FAILED": set(),}
30
+ "UPDATE_FAILED": set(),
31
+ "DELETE_COMPLETE": set(),
32
+ }
29
33
 
30
34
  permitted = allowed.get(current, set())
31
35
  if new_status not in permitted:
@@ -11,6 +11,8 @@ from laniakea_api.auth import fetch_userinfo, create_session_token, verify_sessi
11
11
  from laniakea_api.models import (OIDCLoginRequest, SessionTokenResponse,UserCredentials,
12
12
  CredentialTestRequest, CredentialTestResponse,)
13
13
  from laniakea_api.queue import vault_write_credentials, VAULT_MOUNT
14
+ from laniakea_api.queue import (vault_list_service_creds, vault_read_service_creds,
15
+ vault_write_service_creds, vault_delete_service_creds)
14
16
 
15
17
  router = APIRouter()
16
18
 
@@ -96,3 +98,23 @@ async def test_openstack_credentials(body: CredentialTestRequest,
96
98
  detail=str(exc),
97
99
  )
98
100
 
101
+ @router.get("/profile/service_creds")
102
+ async def list_service_creds(caller: dict = Depends(verify_session_token)):
103
+ return vault_list_service_creds(caller["sub"])
104
+
105
+ @router.get("/profile/service_creds/{name}")
106
+ async def read_service_creds(name: str, caller: dict = Depends(verify_session_token)):
107
+ return vault_read_service_creds(caller["sub"], name)
108
+
109
+ @router.put("/profile/service_creds/{name}")
110
+ async def write_service_creds(name: str, body: dict, caller: dict = Depends(verify_session_token)):
111
+ if not name or "/" in name:
112
+ raise HTTPException(status_code=400, detail="Invalid credential name.")
113
+ body = {k: v for k, v in body.items() if v not in (None, "") and k != "name"}
114
+ vault_write_service_creds(caller["sub"], name, body) # KV2: ogni write = nuova versione
115
+ return {"name": name, "saved": True}
116
+
117
+ @router.delete("/profile/service_creds/{name}")
118
+ async def delete_service_creds(name: str, caller: dict = Depends(verify_session_token)):
119
+ vault_delete_service_creds(caller["sub"], name)
120
+ return {"name": name, "deleted": True}
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "laniakea-api-server"
7
- version = "0.2.4"
7
+ version = "0.2.7"
8
8
  description = "Laniakea Queue API — OIDC-authenticated gateway for cloud deployment orchestration"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }