boxd 0.2.0.dev40__tar.gz → 0.2.1__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 (40) hide show
  1. {boxd-0.2.0.dev40/src/boxd.egg-info → boxd-0.2.1}/PKG-INFO +1 -1
  2. {boxd-0.2.0.dev40 → boxd-0.2.1}/pyproject.toml +1 -1
  3. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_credentials.py +15 -16
  4. {boxd-0.2.0.dev40 → boxd-0.2.1/src/boxd.egg-info}/PKG-INFO +1 -1
  5. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_credentials.py +16 -36
  6. {boxd-0.2.0.dev40 → boxd-0.2.1}/LICENSE +0 -0
  7. {boxd-0.2.0.dev40 → boxd-0.2.1}/README.md +0 -0
  8. {boxd-0.2.0.dev40 → boxd-0.2.1}/setup.cfg +0 -0
  9. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/__init__.py +0 -0
  10. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_client.py +0 -0
  11. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_generated/__init__.py +0 -0
  12. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_generated/api_pb2.py +0 -0
  13. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_generated/api_pb2_grpc.py +0 -0
  14. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_mappers.py +0 -0
  15. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_requests.py +0 -0
  16. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_streaming.py +0 -0
  17. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_transport.py +0 -0
  18. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_urls.py +0 -0
  19. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/_version_check.py +0 -0
  20. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/errors.py +0 -0
  21. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/models.py +0 -0
  22. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/__init__.py +0 -0
  23. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/account.py +0 -0
  24. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/credentials.py +0 -0
  25. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/disks.py +0 -0
  26. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/machines.py +0 -0
  27. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/orgs.py +0 -0
  28. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/snapshots.py +0 -0
  29. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd/resources/vars.py +0 -0
  30. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd.egg-info/SOURCES.txt +0 -0
  31. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd.egg-info/dependency_links.txt +0 -0
  32. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd.egg-info/requires.txt +0 -0
  33. {boxd-0.2.0.dev40 → boxd-0.2.1}/src/boxd.egg-info/top_level.txt +0 -0
  34. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_mappers.py +0 -0
  35. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_namespaces.py +0 -0
  36. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_requests.py +0 -0
  37. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_streaming.py +0 -0
  38. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_transport.py +0 -0
  39. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_urls.py +0 -0
  40. {boxd-0.2.0.dev40 → boxd-0.2.1}/tests/test_version_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boxd
3
- Version: 0.2.0.dev40
3
+ Version: 0.2.1
4
4
  Summary: Python SDK for the boxd cloud VM platform
5
5
  Author: Azin
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "boxd"
3
- version = "0.2.0.dev40"
3
+ version = "0.2.1"
4
4
  description = "Python SDK for the boxd cloud VM platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -35,7 +35,6 @@ METADATA_TOKEN_PATH = "/v1/auth/token"
35
35
  # Proxy-minted, HMAC-signed identity token exposed to every guest process by
36
36
  # takeoff. Forwarded verbatim so the worker attributes the call to the acting
37
37
  # user rather than the VM owner.
38
- IDENTITY_HEADER = "X-Boxd-Identity"
39
38
 
40
39
  # Re-exchange an API-key JWT this long before it expires.
41
40
  EXCHANGE_SKEW_SECS = 300
@@ -78,16 +77,19 @@ def detect_gateway() -> str | None:
78
77
  return None
79
78
 
80
79
 
81
- def vm_id() -> str | None:
82
- """`BOXD_VM_ID`, injected by takeoff at PID 1 present in every guest process."""
83
- value = os.environ.get("BOXD_VM_ID")
84
- return value or None
80
+ # Only a hint: it skips the probe in the common case of running on a laptop.
81
+ # Deliberately permissive which of these a shell actually has depends on how
82
+ # the session was opened and on how old the machine is, so gating on any single
83
+ # one silently disables in-VM auth on machines that are otherwise fine. The
84
+ # metadata probe is the real test: nothing outside a boxd machine answers it.
85
+ _VM_HINTS = ("BOXD_VM_ID", "BOXD_VM_NAME", "BOXD_PROXY_ENDPOINT")
86
+
87
+
88
+ def looks_like_a_machine() -> bool:
89
+ """True when the environment hints we may be inside a boxd machine."""
90
+ return any(os.environ.get(v) for v in _VM_HINTS)
85
91
 
86
92
 
87
- def identity_token() -> str | None:
88
- """`BOXD_IDENTITY`, the session's signed identity token (already base64)."""
89
- value = os.environ.get("BOXD_IDENTITY")
90
- return value or None
91
93
 
92
94
 
93
95
  def probe(host: str, port: int, timeout: float = _PROBE_TIMEOUT_SECS) -> bool:
@@ -185,9 +187,6 @@ class _CredentialsBase:
185
187
  return "exchange"
186
188
  return "auto"
187
189
 
188
- def _headers(self) -> dict[str, str]:
189
- identity = identity_token()
190
- return {IDENTITY_HEADER: identity} if identity else {}
191
190
 
192
191
 
193
192
  class SyncCredentials(_CredentialsBase):
@@ -222,7 +221,7 @@ class SyncCredentials(_CredentialsBase):
222
221
  base = self._metadata_base()
223
222
  try:
224
223
  with httpx.Client(timeout=_HTTP_TIMEOUT_SECS) as client:
225
- resp = client.get(base + METADATA_TOKEN_PATH, headers=self._headers())
224
+ resp = client.get(base + METADATA_TOKEN_PATH)
226
225
  except httpx.HTTPError as e:
227
226
  raise AuthenticationError(f"in-VM token request failed: {e}") from e
228
227
  token, expires_at, api_url = parse_vm_token(
@@ -234,7 +233,7 @@ class SyncCredentials(_CredentialsBase):
234
233
 
235
234
  def _metadata_base(self) -> str:
236
235
  """Detection needs BOTH signals — neither alone is sufficient."""
237
- if not vm_id():
236
+ if not looks_like_a_machine():
238
237
  raise AuthenticationError(_NO_CREDENTIALS)
239
238
  gateway = detect_gateway()
240
239
  if not gateway or not probe(gateway, METADATA_PORT):
@@ -277,7 +276,7 @@ class AsyncCredentials(_CredentialsBase):
277
276
  try:
278
277
  async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT_SECS) as client:
279
278
  resp = await client.get(
280
- base + METADATA_TOKEN_PATH, headers=self._headers()
279
+ base + METADATA_TOKEN_PATH
281
280
  )
282
281
  except httpx.HTTPError as e:
283
282
  raise AuthenticationError(f"in-VM token request failed: {e}") from e
@@ -289,7 +288,7 @@ class AsyncCredentials(_CredentialsBase):
289
288
  self.discovered_base_url = api_url
290
289
 
291
290
  async def _metadata_base(self) -> str:
292
- if not vm_id():
291
+ if not looks_like_a_machine():
293
292
  raise AuthenticationError(_NO_CREDENTIALS)
294
293
  gateway = detect_gateway()
295
294
  if not gateway or not await probe_async(gateway, METADATA_PORT):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boxd
3
- Version: 0.2.0.dev40
3
+ Version: 0.2.1
4
4
  Summary: Python SDK for the boxd cloud VM platform
5
5
  Author: Azin
6
6
  License-Expression: MIT
@@ -141,42 +141,6 @@ def test_both_signals_mint_a_token_and_report_the_cluster(monkeypatch):
141
141
  assert c.discovered_base_url == "http://boxd-stg.sh:9443"
142
142
 
143
143
 
144
- def test_identity_is_forwarded_when_set(monkeypatch):
145
- monkeypatch.setenv("BOXD_VM_ID", "vm-123")
146
- monkeypatch.setenv("BOXD_IDENTITY", "signed-identity")
147
- monkeypatch.setattr("boxd._credentials.detect_gateway", lambda: "10.42.0.1")
148
- monkeypatch.setattr("boxd._credentials.probe", lambda *a, **k: True)
149
-
150
- seen = {}
151
-
152
- class FakeResponse:
153
- status_code = 200
154
- text = "{}"
155
-
156
- def json(self):
157
- return {"token": "vm-jwt", "expires_at": time.time() + 900}
158
-
159
- class FakeClient:
160
- def __init__(self, **kwargs):
161
- pass
162
-
163
- def __enter__(self):
164
- return self
165
-
166
- def __exit__(self, *a):
167
- return False
168
-
169
- def get(self, url, headers=None):
170
- seen.update(headers or {})
171
- return FakeResponse()
172
-
173
- monkeypatch.setattr("boxd._credentials.httpx.Client", FakeClient)
174
- creds().token()
175
- assert seen["X-Boxd-Identity"] == "signed-identity"
176
-
177
-
178
- # --- gateway parsing ---
179
-
180
144
 
181
145
  def test_detect_gateway_parses_the_default_route(monkeypatch, tmp_path):
182
146
  route = tmp_path / "route"
@@ -303,3 +267,19 @@ async def test_async_no_credentials_raises(monkeypatch):
303
267
  c = AsyncCredentials(api_key=None, token=None, exchange_url=EXCHANGE)
304
268
  with pytest.raises(AuthenticationError, match="no credentials"):
305
269
  await c.token()
270
+
271
+
272
+ def test_in_vm_detection_does_not_hinge_on_one_env_var(monkeypatch):
273
+ # A real dev machine's shell had BOXD_IDENTITY and BOXD_PROXY_ENDPOINT but
274
+ # no BOXD_VM_ID — gating on BOXD_VM_ID alone disabled in-VM auth there.
275
+ from boxd import _credentials as c
276
+
277
+ for v in c._VM_HINTS:
278
+ monkeypatch.delenv(v, raising=False)
279
+ assert c.looks_like_a_machine() is False
280
+
281
+ for present in c._VM_HINTS:
282
+ for v in c._VM_HINTS:
283
+ monkeypatch.delenv(v, raising=False)
284
+ monkeypatch.setenv(present, "x")
285
+ assert c.looks_like_a_machine() is True, present
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