Skip to content

Commit 2eb9419

Browse files
Route all API calls through Secure Gateway proxy and rename LICENSE_SERVER (#2254)
* Route all API calls through secure gateway proxy when configured Rename LICENSE_SERVER to SECURE_GATEWAY with deprecation fallback and wrap all external HTTP requests with wrap_url() so every outbound call routes through the secure gateway in air-gapped deployments. Also remove runtime GitHub download of GroundingDINO config file, resolving the path from the installed package instead. * Add changes to version, adjust deprecation warnings and prepare changelog * Bump inference requirements --------- Co-authored-by: Paweł Pęczek <pawel@roboflow.com>
1 parent 33928bb commit 2eb9419

24 files changed

Lines changed: 296 additions & 94 deletions

File tree

docs/quickstart/docker_configuration_options.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@ Sets the max batch size accepted by the clip model inference functions.
5959

6060
If true, the batch size will be fixed to the maximum batch size configured for this server.
6161

62-
## License Server
62+
## Secure Gateway
6363

64-
**LICENSE_SERVER**: String (default = None)
64+
**SECURE_GATEWAY**: String (default = None)
6565

66-
Sets the address of a Roboflow license server.
66+
Sets the address of a Roboflow Secure Gateway for air-gapped deployments. All API and model download traffic will be routed through this proxy.
67+
68+
The legacy `LICENSE_SERVER` environment variable is still accepted but deprecated.
6769

6870
## Maximum Active Models
6971

inference/core/env.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,17 @@
367367
# Flag to enable legacy route, default is True
368368
LEGACY_ROUTE_ENABLED = str2bool(os.getenv("LEGACY_ROUTE_ENABLED", True))
369369

370-
# License server, default is None
371-
LICENSE_SERVER = os.getenv("LICENSE_SERVER", None)
370+
# Secure gateway address for air-gapped deployments.
371+
# Accepts SECURE_GATEWAY (preferred) or LICENSE_SERVER (legacy).
372+
_legacy_license_server = os.getenv("LICENSE_SERVER")
373+
SECURE_GATEWAY = os.getenv("SECURE_GATEWAY") or _legacy_license_server or None
374+
if _legacy_license_server and not os.getenv("SECURE_GATEWAY"):
375+
warnings.warn(
376+
"`LICENSE_SERVER` env variable is deprecated, use `SECURE_GATEWAY` instead. "
377+
"`LICENSE_SERVER` will be removed end of Q3 2026.",
378+
DeprecationWarning,
379+
stacklevel=1,
380+
)
372381

373382
# Log level, default is "WARNING"
374383
LOG_LEVEL = os.getenv("LOG_LEVEL", "WARNING")

inference/core/interfaces/http/http_api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@
287287
)
288288
from inference.core.utils.container import is_docker_socket_mounted
289289
from inference.core.utils.notebooks import start_notebook
290+
from inference.core.utils.url_utils import wrap_url
290291
from inference.core.workflows.core_steps.common.entities import StepExecutionMode
291292
from inference.core.workflows.errors import (
292293
WorkflowBlockError,
@@ -3354,7 +3355,7 @@ def sam3_segment_image(
33543355
)
33553356

33563357
response = requests.post(
3357-
f"{endpoint}?api_key={api_key}",
3358+
wrap_url(f"{endpoint}?api_key={api_key}"),
33583359
json=payload,
33593360
headers=headers,
33603361
timeout=60,
@@ -3455,7 +3456,7 @@ def sam3_visual_segment(
34553456
)
34563457

34573458
response = requests.post(
3458-
f"{endpoint}?api_key={api_key}",
3459+
wrap_url(f"{endpoint}?api_key={api_key}"),
34593460
json=payload,
34603461
headers=headers,
34613462
timeout=60,

inference/core/interfaces/webrtc_worker/watchdog.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212
from inference.core.interfaces.webrtc_worker.utils import is_over_quota
1313
from inference.core.logger import logger
14+
from inference.core.utils.url_utils import wrap_url
1415

1516

1617
class Watchdog:
@@ -87,7 +88,7 @@ def _send_session_heartbeat(self):
8788

8889
try:
8990
response = requests.post(
90-
self._heartbeat_url,
91+
wrap_url(self._heartbeat_url),
9192
json={
9293
"session_id": self._session_id,
9394
"api_key": self._api_key,
@@ -116,7 +117,7 @@ def _send_session_heartbeat_stop(self):
116117
url = self._heartbeat_url + "/end"
117118
try:
118119
response = requests.post(
119-
url,
120+
wrap_url(url),
120121
json={
121122
"session_id": self._session_id,
122123
"api_key": self._api_key,

inference/core/roboflow_api.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from cachetools.func import ttl_cache
2121
from requests import Response, Timeout
2222
from requests_toolbelt import MultipartEncoder
23+
from yarl import URL
2324

2425
from inference.core import logger
2526
from inference.core.cache import cache
@@ -290,10 +291,15 @@ def get_roboflow_workspace(api_key: str) -> WorkspaceID:
290291
async def get_roboflow_workspace_async(api_key: str) -> WorkspaceID:
291292
try:
292293
headers = build_roboflow_api_headers()
294+
full_url = wrap_url(
295+
_add_params_to_url(
296+
url=f"{API_BASE_URL}/",
297+
params=[("api_key", api_key), ("nocache", "true")],
298+
)
299+
)
293300
async with aiohttp.ClientSession() as session:
294301
async with session.get(
295-
f"{API_BASE_URL}/",
296-
params={"api_key": api_key, "nocache": "true"},
302+
URL(full_url, encoded=True),
297303
headers=headers,
298304
timeout=ROBOFLOW_API_REQUEST_TIMEOUT,
299305
) as response:
@@ -332,10 +338,15 @@ async def get_serverless_usage_check_async(
332338
) -> ServerlessUsageCheckResponse:
333339
try:
334340
headers = build_roboflow_api_headers()
341+
full_url = wrap_url(
342+
_add_params_to_url(
343+
url=f"{API_BASE_URL}/serverless/usage-check",
344+
params=[("api_key", api_key), ("nocache", "true")],
345+
)
346+
)
335347
async with aiohttp.ClientSession() as session:
336348
async with session.get(
337-
f"{API_BASE_URL}/serverless/usage-check",
338-
params={"api_key": api_key, "nocache": "true"},
349+
URL(full_url, encoded=True),
339350
headers=headers,
340351
timeout=ROBOFLOW_API_REQUEST_TIMEOUT,
341352
) as response:
@@ -389,9 +400,11 @@ def add_custom_metadata(
389400
field_name: str,
390401
field_value: str,
391402
):
392-
api_url = _add_params_to_url(
393-
url=f"{API_BASE_URL}/{workspace_id}/inference-stats/metadata",
394-
params=[("api_key", api_key), ("nocache", "true")],
403+
api_url = wrap_url(
404+
_add_params_to_url(
405+
url=f"{API_BASE_URL}/{workspace_id}/inference-stats/metadata",
406+
params=[("api_key", api_key), ("nocache", "true")],
407+
)
395408
)
396409
response = requests.post(
397410
url=api_url,
@@ -1126,7 +1139,9 @@ def _test_range_request(url: str, timeout: int = 10) -> bool:
11261139
"""
11271140
try:
11281141
headers = {"Range": "bytes=0-0"}
1129-
response = requests.get(url, headers=headers, stream=True, timeout=timeout)
1142+
response = requests.get(
1143+
wrap_url(url), headers=headers, stream=True, timeout=timeout
1144+
)
11301145
response.close()
11311146
if response.status_code == 206:
11321147
return True
@@ -1200,9 +1215,11 @@ def send_inference_results_to_model_monitoring(
12001215
workspace_id: WorkspaceID,
12011216
inference_data: dict,
12021217
):
1203-
api_url = _add_params_to_url(
1204-
url=f"{API_BASE_URL}/{workspace_id}/inference-stats",
1205-
params=[("api_key", api_key)],
1218+
api_url = wrap_url(
1219+
_add_params_to_url(
1220+
url=f"{API_BASE_URL}/{workspace_id}/inference-stats",
1221+
params=[("api_key", api_key)],
1222+
)
12061223
)
12071224
response = requests.post(
12081225
url=api_url,

inference/core/utils/url_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import urllib
22

3-
from inference.core.env import LICENSE_SERVER
3+
from inference.core.env import SECURE_GATEWAY
44

55

66
def wrap_url(url: str) -> str:
7-
if not LICENSE_SERVER:
7+
if not SECURE_GATEWAY:
88
return url
9-
return f"http://{LICENSE_SERVER}/proxy?url=" + urllib.parse.quote(
9+
return f"http://{SECURE_GATEWAY}/proxy?url=" + urllib.parse.quote(
1010
url, safe="~()*!'"
1111
)

inference/core/workflows/core_steps/models/foundation/seg_preview/v1.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
)
1919
from inference.core.managers.base import ModelManager
2020
from inference.core.roboflow_api import build_roboflow_api_headers
21+
from inference.core.utils.url_utils import wrap_url
2122
from inference.core.workflows.core_steps.common.entities import StepExecutionMode
2223
from inference.core.workflows.core_steps.common.utils import (
2324
attach_parents_coordinates_to_batch_of_sv_detections,
@@ -200,7 +201,7 @@ def run_via_request(
200201
headers = build_roboflow_api_headers(explicit_headers=headers)
201202

202203
response = requests.post(
203-
f"{endpoint}?api_key={api_key}",
204+
wrap_url(f"{endpoint}?api_key={api_key}"),
204205
json=payload,
205206
headers=headers,
206207
timeout=60,

inference/core/workflows/core_steps/models/foundation/segment_anything3/v1.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from inference.core.managers.base import ModelManager
2828
from inference.core.roboflow_api import build_roboflow_api_headers
29+
from inference.core.utils.url_utils import wrap_url
2930
from inference.core.workflows.core_steps.common.entities import StepExecutionMode
3031
from inference.core.workflows.core_steps.common.utils import (
3132
attach_parents_coordinates_to_batch_of_sv_detections,
@@ -404,7 +405,7 @@ def run_via_request(
404405
headers = build_roboflow_api_headers(explicit_headers=headers)
405406

406407
response = requests.post(
407-
f"{endpoint}?api_key={api_key}",
408+
wrap_url(f"{endpoint}?api_key={api_key}"),
408409
json=payload,
409410
headers=headers,
410411
timeout=60,

inference/core/workflows/core_steps/models/foundation/segment_anything3/v2.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from inference.core.managers.base import ModelManager
2828
from inference.core.roboflow_api import build_roboflow_api_headers
29+
from inference.core.utils.url_utils import wrap_url
2930
from inference.core.workflows.core_steps.common.entities import StepExecutionMode
3031
from inference.core.workflows.core_steps.common.utils import (
3132
attach_parents_coordinates_to_batch_of_sv_detections,
@@ -493,7 +494,7 @@ def run_via_request(
493494
headers = build_roboflow_api_headers(explicit_headers=headers)
494495

495496
response = requests.post(
496-
f"{endpoint}?api_key={api_key}",
497+
wrap_url(f"{endpoint}?api_key={api_key}"),
497498
json=payload,
498499
headers=headers,
499500
timeout=60,

inference/core/workflows/core_steps/models/foundation/segment_anything3/v3.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from inference.core.managers.base import ModelManager
2828
from inference.core.roboflow_api import build_roboflow_api_headers
29+
from inference.core.utils.url_utils import wrap_url
2930
from inference.core.workflows.core_steps.common.entities import StepExecutionMode
3031
from inference.core.workflows.core_steps.common.utils import (
3132
attach_parents_coordinates_to_batch_of_sv_detections,
@@ -536,7 +537,7 @@ def run_via_request(
536537
headers = build_roboflow_api_headers(explicit_headers=headers)
537538

538539
response = requests.post(
539-
f"{endpoint}?api_key={api_key}",
540+
wrap_url(f"{endpoint}?api_key={api_key}"),
540541
json=payload,
541542
headers=headers,
542543
timeout=60,

0 commit comments

Comments
 (0)