Skip to content

Commit ae3b6be

Browse files
authored
Fix http protocol shown for https services with acm cert (#3709)
* Fix http protocol shown for https services with acm cert * Fix tests * Disallow http services on gateways with ACM cert
1 parent ce7e75f commit ae3b6be

File tree

2 files changed

+85
-13
lines changed

2 files changed

+85
-13
lines changed

src/dstack/_internal/server/services/services/__init__.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,29 @@ async def _register_service_in_gateway(
157157
f"'{gateway.name}' does not have the SGLang router configured."
158158
)
159159

160-
service_https = _get_service_https(run_spec, gateway_configuration)
161-
router = _build_service_router_config(gateway_configuration, run_spec.configuration)
162-
service_protocol = "https" if service_https else "http"
160+
configure_service_https = _should_configure_service_https_on_gateway(
161+
run_spec, gateway_configuration
162+
)
163+
show_service_https = _should_show_service_https(run_spec, gateway_configuration)
164+
service_protocol = "https" if show_service_https else "http"
165+
166+
if (
167+
not show_service_https
168+
and gateway_configuration.certificate is not None
169+
and gateway_configuration.certificate.type == "acm"
170+
):
171+
# SSL termination is done globally at load balancer so cannot runs only some services via http.
172+
raise ServerClientError(
173+
"Cannot run HTTP service on gateway with ACM certificates configured"
174+
)
163175

164-
if service_https and gateway_configuration.certificate is None:
176+
if show_service_https and gateway_configuration.certificate is None:
165177
raise ServerClientError(
166178
"Cannot run HTTPS service on gateway with no SSL certificates configured"
167179
)
168180

181+
router = _build_service_router_config(gateway_configuration, run_spec.configuration)
182+
169183
gateway_https = _get_gateway_https(gateway_configuration)
170184
gateway_protocol = "https" if gateway_https else "http"
171185

@@ -195,7 +209,7 @@ async def _register_service_in_gateway(
195209
project=run_model.project.name,
196210
run_name=run_model.run_name,
197211
domain=domain,
198-
service_https=service_https,
212+
service_https=configure_service_https,
199213
gateway_https=gateway_https,
200214
auth=run_spec.configuration.auth,
201215
client_max_body_size=settings.DEFAULT_SERVICE_CLIENT_MAX_BODY_SIZE,
@@ -432,7 +446,13 @@ async def unregister_replica(session: AsyncSession, job_model: JobModel):
432446
)
433447

434448

435-
def _get_service_https(run_spec: RunSpec, configuration: GatewayConfiguration) -> bool:
449+
def _should_configure_service_https_on_gateway(
450+
run_spec: RunSpec, configuration: GatewayConfiguration
451+
) -> bool:
452+
"""
453+
Returns `True` if the gateway needs to serve the service with HTTPS.
454+
May be `False` for HTTPS services, e.g. SSL termination is done on a load balancer.
455+
"""
436456
assert run_spec.configuration.type == "service"
437457
https = run_spec.configuration.https
438458
if https is None:
@@ -450,6 +470,21 @@ def _get_service_https(run_spec: RunSpec, configuration: GatewayConfiguration) -
450470
return True
451471

452472

473+
def _should_show_service_https(run_spec: RunSpec, configuration: GatewayConfiguration) -> bool:
474+
"""
475+
Returns `True` if the service needs to be accessed via https://.
476+
"""
477+
assert run_spec.configuration.type == "service"
478+
https = run_spec.configuration.https
479+
if https is None:
480+
https = SERVICE_HTTPS_DEFAULT
481+
if https == "auto":
482+
if configuration.certificate is None:
483+
return False
484+
return True
485+
return https
486+
487+
453488
def _get_gateway_https(configuration: GatewayConfiguration) -> bool:
454489
if configuration.certificate is not None and configuration.certificate.type == "acm":
455490
return False

src/tests/_internal/server/services/services/test_services.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
)
1515
from dstack._internal.core.models.runs import RunSpec
1616
from dstack._internal.server.services.services import (
17-
_get_service_https,
1817
_register_service_in_server,
18+
_should_configure_service_https_on_gateway,
19+
_should_show_service_https,
1920
)
2021
from dstack._internal.server.testing.common import get_run_spec
2122

@@ -54,33 +55,69 @@ def test_accepts_auto(self) -> None:
5455
assert conf.https == "auto"
5556

5657

57-
class TestGetServiceHttps:
58+
class TestShouldConfigureServiceHttpsOnGateway:
5859
def test_auto_resolves_to_true_with_lets_encrypt_gateway(self) -> None:
5960
run_spec = _service_run_spec(https="auto")
6061
gw = _gateway_config(certificate=LetsEncryptGatewayCertificate())
61-
assert _get_service_https(run_spec, gw) is True
62+
assert _should_configure_service_https_on_gateway(run_spec, gw) is True
6263

6364
def test_auto_resolves_to_false_when_gateway_has_no_certificate(self) -> None:
6465
run_spec = _service_run_spec(https="auto")
6566
gw = _gateway_config(certificate=None)
66-
assert _get_service_https(run_spec, gw) is False
67+
assert _should_configure_service_https_on_gateway(run_spec, gw) is False
6768

6869
def test_auto_resolves_to_false_with_acm_gateway(self) -> None:
6970
run_spec = _service_run_spec(https="auto")
7071
gw = _gateway_config(
7172
certificate=ACMGatewayCertificate(arn="arn:aws:acm:us-east-1:123:cert/abc")
7273
)
73-
assert _get_service_https(run_spec, gw) is False
74+
assert _should_configure_service_https_on_gateway(run_spec, gw) is False
75+
76+
def test_true_enables_https_when_gateway_has_no_certificate(self) -> None:
77+
run_spec = _service_run_spec(https=True)
78+
gw = _gateway_config(certificate=None)
79+
assert _should_configure_service_https_on_gateway(run_spec, gw) is True
80+
81+
def test_false_disables_https_regardless_of_gateway_certificate(self) -> None:
82+
run_spec = _service_run_spec(https=False)
83+
gw = _gateway_config(certificate=LetsEncryptGatewayCertificate())
84+
assert _should_configure_service_https_on_gateway(run_spec, gw) is False
85+
86+
def test_true_does_not_configure_https_on_acm_gateway(self) -> None:
87+
run_spec = _service_run_spec(https=True)
88+
gw = _gateway_config(
89+
certificate=ACMGatewayCertificate(arn="arn:aws:acm:us-east-1:123:cert/abc")
90+
)
91+
assert _should_configure_service_https_on_gateway(run_spec, gw) is False
92+
93+
94+
class TestShouldShowServiceHttps:
95+
def test_auto_resolves_to_true_with_lets_encrypt_gateway(self) -> None:
96+
run_spec = _service_run_spec(https="auto")
97+
gw = _gateway_config(certificate=LetsEncryptGatewayCertificate())
98+
assert _should_show_service_https(run_spec, gw) is True
99+
100+
def test_auto_resolves_to_false_when_gateway_has_no_certificate(self) -> None:
101+
run_spec = _service_run_spec(https="auto")
102+
gw = _gateway_config(certificate=None)
103+
assert _should_show_service_https(run_spec, gw) is False
104+
105+
def test_auto_resolves_to_true_with_acm_gateway(self) -> None:
106+
run_spec = _service_run_spec(https="auto")
107+
gw = _gateway_config(
108+
certificate=ACMGatewayCertificate(arn="arn:aws:acm:us-east-1:123:cert/abc")
109+
)
110+
assert _should_show_service_https(run_spec, gw) is True
74111

75112
def test_true_enables_https_regardless_of_gateway_certificate(self) -> None:
76113
run_spec = _service_run_spec(https=True)
77114
gw = _gateway_config(certificate=None)
78-
assert _get_service_https(run_spec, gw) is True
115+
assert _should_show_service_https(run_spec, gw) is True
79116

80117
def test_false_disables_https_regardless_of_gateway_certificate(self) -> None:
81118
run_spec = _service_run_spec(https=False)
82119
gw = _gateway_config(certificate=LetsEncryptGatewayCertificate())
83-
assert _get_service_https(run_spec, gw) is False
120+
assert _should_show_service_https(run_spec, gw) is False
84121

85122

86123
class TestRegisterServiceInServerHttps:

0 commit comments

Comments
 (0)