Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit e12b591

Browse files
authored
Merge pull request #464 from jumpstarter-dev/backport-462-to-release-0.6
Backport #462 to release 0.6
2 parents 282c26d + b6a0960 commit e12b591

File tree

8 files changed

+216
-9
lines changed

8 files changed

+216
-9
lines changed

docs/source/getting-started/configuration/authentication.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ Note, the HTTPS URL is mandatory, and you only need to include certificateAuthor
4646
prefix usernames with `keycloak:` as configured in the claim mappings:
4747

4848
```shell
49-
$ jmp admin create client test-client --oidc-username keycloak:developer-1
49+
$ jmp admin create client test-client --insecure-tls-config --oidc-username keycloak:developer-1
5050
```
5151

5252
4. Instruct users to log in with:
5353

5454
```shell
5555
$ jmp login --client <client alias> \
56+
--insecure-tls-config \
5657
--endpoint <jumpstarter controller endpoint> \
5758
--namespace <namespace> --name <client name> \
5859
--issuer https://<keycloak domain>/realms/<realm name>
@@ -62,6 +63,7 @@ For non-interactive login, add username and password:
6263

6364
```shell
6465
$ jmp login --client <client alias> [other parameters] \
66+
--insecure-tls-config \
6567
--username <username> \
6668
--password <password>
6769
```
@@ -76,6 +78,7 @@ For exporters, use similar login command but with the `--exporter` flag:
7678

7779
```shell
7880
$ jmp login --exporter <exporter alias> \
81+
--insecure-tls-config \
7982
--endpoint <jumpstarter controller endpoint> \
8083
--namespace <namespace> --name <exporter name> \
8184
--issuer https://<keycloak domain>/realms/<realm name>
@@ -188,6 +191,7 @@ jwt:
188191

189192
```shell
190193
$ jmp admin create exporter test-exporter \
194+
--insecure-tls-config \
191195
--oidc-username dex:system:serviceaccount:default:test-service-account
192196
```
193197

@@ -197,6 +201,7 @@ For clients:
197201

198202
```shell
199203
$ jmp login --client <client alias> \
204+
--insecure-tls-config \
200205
--endpoint <jumpstarter controller endpoint> \
201206
--namespace <namespace> --name <client name> \
202207
--issuer https://dex.dex.svc.cluster.local:5556 \
@@ -208,6 +213,7 @@ For exporters:
208213

209214
```shell
210215
$ jmp login --exporter <exporter alias> \
216+
--insecure-tls-config \
211217
--endpoint <jumpstarter controller endpoint> \
212218
--namespace <namespace> --name <exporter name> \
213219
--issuer https://dex.dex.svc.cluster.local:5556 \

docs/source/getting-started/usage/setup-distributed-mode.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
This guide walks you through the process of creating an exporter using the
44
controller service, configuring drivers, and running the exporter.
55

6+
```{warning}
7+
The jumpstarter-controller endpoints are secured by TLS. However, in release 0.6.x,
8+
the certificates are self-signed and rotated on every restart. This means the client
9+
will not be able to verify the server certificate. To bypass this, you should use the
10+
`--insecure-tls-config` flag when creating clients and exporters. This issue will be
11+
resolved in the next release. See [issue #455](https://github.com/jumpstarter-dev/jumpstarter/issues/455)
12+
for more details.
13+
Alternatively, you can configure the ingress/route in reencrypt mode with your own key and certificate.
14+
```
15+
616
## Prerequisites
717

818
Install [the following packages](../installation/packages.md) in your Python
@@ -30,7 +40,7 @@ Run this command to create an exporter named `example-distributed` and save the
3040
configuration locally:
3141

3242
```shell
33-
$ jmp admin create exporter example-distributed --save
43+
$ jmp admin create exporter example-distributed --save --insecure-tls-config
3444
```
3545

3646
After creating the exporter, find the new configuration file at
@@ -78,7 +88,7 @@ development purposes, and saves the configuration locally in
7888
`${HOME}/.config/jumpstarter/clients/`:
7989

8090
```shell
81-
$ jmp admin create client hello --save --unsafe
91+
$ jmp admin create client hello --save --unsafe --insecure-tls-config
8292
```
8393

8494
### Spawn an Exporter Shell

packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from jumpstarter_cli_common.opt import (
77
OutputMode,
88
OutputType,
9+
confirm_insecure_tls,
910
opt_context,
11+
opt_insecure_tls_config,
1012
opt_kubeconfig,
1113
opt_labels,
1214
opt_log_level,
@@ -75,13 +77,15 @@ def print_created_client(client: V1Alpha1Client, output: OutputType):
7577
@opt_labels
7678
@opt_kubeconfig
7779
@opt_context
80+
@opt_insecure_tls_config
7881
@opt_oidc_username
7982
@opt_nointeractive
8083
@opt_output_all
8184
async def create_client(
8285
name: Optional[str],
8386
kubeconfig: Optional[str],
8487
context: Optional[str],
88+
insecure_tls_config: bool,
8589
namespace: str,
8690
labels: list[(str, str)],
8791
save: bool,
@@ -94,6 +98,7 @@ async def create_client(
9498
):
9599
"""Create a client object in the Kubernetes cluster"""
96100
try:
101+
confirm_insecure_tls(insecure_tls_config, nointeractive)
97102
async with ClientsV1Alpha1Api(namespace, kubeconfig, context) as api:
98103
if output is None:
99104
# Only print status if is not JSON/YAML
@@ -113,6 +118,7 @@ async def create_client(
113118
allow_drivers = allow.split(",") if allow is not None and len(allow) > 0 else []
114119
client_config.drivers.unsafe = unsafe
115120
client_config.drivers.allow = allow_drivers
121+
client_config.tls.insecure = insecure_tls_config
116122
ClientConfigV1Alpha1.save(client_config, out)
117123
# If this is the only client config, set it as default
118124
if out is None and len(ClientConfigV1Alpha1.list()) == 1:
@@ -156,13 +162,15 @@ def print_created_exporter(exporter: V1Alpha1Exporter, output: OutputType):
156162
@opt_labels
157163
@opt_kubeconfig
158164
@opt_context
165+
@opt_insecure_tls_config
159166
@opt_oidc_username
160167
@opt_nointeractive
161168
@opt_output_all
162169
async def create_exporter(
163170
name: Optional[str],
164171
kubeconfig: Optional[str],
165172
context: Optional[str],
173+
insecure_tls_config: bool,
166174
namespace: str,
167175
labels: list[(str, str)],
168176
save: bool,
@@ -173,6 +181,7 @@ async def create_exporter(
173181
):
174182
"""Create an exporter object in the Kubernetes cluster"""
175183
try:
184+
confirm_insecure_tls(insecure_tls_config, nointeractive)
176185
async with ExportersV1Alpha1Api(namespace, kubeconfig, context) as api:
177186
if output is None:
178187
click.echo(f"Creating exporter '{name}' in namespace '{namespace}'")
@@ -182,6 +191,7 @@ async def create_exporter(
182191
if output is None:
183192
click.echo("Fetching exporter credentials from cluster")
184193
exporter_config = await api.get_exporter_config(name)
194+
exporter_config.tls.insecure = insecure_tls_config
185195
ExporterConfigV1Alpha1.save(exporter_config, out)
186196
if output is None:
187197
click.echo(f"Exporter configuration successfully saved to {exporter_config.path}")

packages/jumpstarter-cli-admin/jumpstarter_cli_admin/create_test.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid
2+
from pathlib import Path
23
from unittest.mock import AsyncMock, Mock, patch
34

45
import pytest
@@ -17,6 +18,7 @@
1718
from jumpstarter.config.client import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers
1819
from jumpstarter.config.common import ObjectMeta
1920
from jumpstarter.config.exporter import ExporterConfigV1Alpha1
21+
from jumpstarter.config.tls import TLSConfigV1Alpha1
2022

2123
# Generate a random client name
2224
CLIENT_NAME = uuid.uuid4().hex
@@ -82,6 +84,15 @@
8284
drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True),
8385
)
8486

87+
INSECURE_TLS_CLIENT_CONFIG = ClientConfigV1Alpha1(
88+
alias=CLIENT_NAME,
89+
metadata=ObjectMeta(namespace="default", name=CLIENT_NAME),
90+
endpoint=CLIENT_ENDPOINT,
91+
token=CLIENT_TOKEN,
92+
tls=TLSConfigV1Alpha1(insecure=True),
93+
drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True),
94+
)
95+
8596

8697
@pytest.mark.anyio
8798
@patch.object(ClientConfigV1Alpha1, "save")
@@ -102,6 +113,33 @@ async def test_create_client(
102113
mock_save_client.assert_not_called()
103114
mock_save_client.reset_mock()
104115

116+
# Insecure TLS config is returned
117+
mock_get_client_config.return_value = INSECURE_TLS_CLIENT_CONFIG
118+
119+
# Save with prompts accept insecure = Y, save = Y, unsafe = Y
120+
result = await runner.invoke(create, ["client", "--insecure-tls-config", CLIENT_NAME], input="Y\nY\nY\n")
121+
assert result.exit_code == 0
122+
assert "Client configuration successfully saved" in result.output
123+
mock_save_client.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None)
124+
mock_save_client.reset_mock()
125+
126+
# Save no interactive and insecure tls
127+
result = await runner.invoke(
128+
create, ["client", "--insecure-tls-config", "--unsafe", "--save", "--nointeractive", CLIENT_NAME]
129+
)
130+
assert result.exit_code == 0
131+
assert "Client configuration successfully saved" in result.output
132+
mock_save_client.assert_called_once_with(INSECURE_TLS_CLIENT_CONFIG, None)
133+
mock_save_client.reset_mock()
134+
135+
# Insecure TLS config is returned
136+
mock_get_client_config.return_value = INSECURE_TLS_CLIENT_CONFIG
137+
138+
# Save with prompts accept insecure = N
139+
result = await runner.invoke(create, ["client", "--insecure-tls-config", CLIENT_NAME], input="n\n")
140+
assert result.exit_code == 1
141+
assert "Aborted" in result.output
142+
105143
# Unsafe client config is returned
106144
mock_get_client_config.return_value = UNSAFE_CLIENT_CONFIG
107145

@@ -117,7 +155,7 @@ async def test_create_client(
117155
result = await runner.invoke(create, ["client", CLIENT_NAME, "--unsafe", "--out", out], input="\n\n")
118156
assert result.exit_code == 0
119157
assert "Client configuration successfully saved" in result.output
120-
mock_save_client.assert_called_once_with(UNSAFE_CLIENT_CONFIG, out)
158+
mock_save_client.assert_called_once_with(UNSAFE_CLIENT_CONFIG, str(Path(out).resolve()))
121159
mock_save_client.reset_mock()
122160

123161
# Regular client config is returned
@@ -221,6 +259,14 @@ async def test_create_client(
221259
token=EXPORTER_TOKEN,
222260
)
223261

262+
INSECURE_TLS_EXPORTER_CONFIG = ExporterConfigV1Alpha1(
263+
alias=EXPORTER_NAME,
264+
metadata=ObjectMeta(namespace="default", name=EXPORTER_NAME),
265+
endpoint=EXPORTER_ENDPOINT,
266+
token=EXPORTER_TOKEN,
267+
tls=TLSConfigV1Alpha1(insecure=True),
268+
)
269+
224270

225271
@pytest.mark.anyio
226272
@patch.object(ExporterConfigV1Alpha1, "save")
@@ -241,6 +287,32 @@ async def test_create_exporter(
241287
save_exporter_mock.assert_not_called()
242288
save_exporter_mock.reset_mock()
243289

290+
# Insecure TLS config is returned
291+
_get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG
292+
# Save with prompts accept insecure = Y, save = Y
293+
result = await runner.invoke(create, ["exporter", "--insecure-tls-config", EXPORTER_NAME], input="Y\nY\n")
294+
assert result.exit_code == 0
295+
assert "Exporter configuration successfully saved" in result.output
296+
save_exporter_mock.assert_called_once_with(INSECURE_TLS_EXPORTER_CONFIG, None)
297+
save_exporter_mock.reset_mock()
298+
299+
_get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG
300+
# Save with prompts accept no interactive
301+
result = await runner.invoke(
302+
create, ["exporter", "--insecure-tls-config", "--nointeractive", "--save", EXPORTER_NAME]
303+
)
304+
assert result.exit_code == 0
305+
assert "Exporter configuration successfully saved" in result.output
306+
save_exporter_mock.assert_called_once_with(INSECURE_TLS_EXPORTER_CONFIG, None)
307+
save_exporter_mock.reset_mock()
308+
309+
# Insecure TLS config is returned
310+
_get_exporter_config_mock.return_value = INSECURE_TLS_EXPORTER_CONFIG
311+
# Save with prompts accept insecure = N
312+
result = await runner.invoke(create, ["exporter", "--insecure-tls-config", EXPORTER_NAME], input="n\n")
313+
assert result.exit_code == 1
314+
assert "Aborted" in result.output
315+
244316
# Save with prompts
245317
result = await runner.invoke(create, ["exporter", EXPORTER_NAME], input="Y\n")
246318
assert result.exit_code == 0
@@ -260,7 +332,7 @@ async def test_create_exporter(
260332
result = await runner.invoke(create, ["exporter", EXPORTER_NAME, "--out", out])
261333
assert result.exit_code == 0
262334
assert "Exporter configuration successfully saved" in result.output
263-
save_exporter_mock.assert_called_once_with(EXPORTER_CONFIG, out)
335+
save_exporter_mock.assert_called_once_with(EXPORTER_CONFIG, str(Path(out).resolve()))
264336
save_exporter_mock.reset_mock()
265337

266338
# Save with nointeractive

packages/jumpstarter-cli-admin/jumpstarter_cli_admin/import_res.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import asyncclick as click
44
from jumpstarter_cli_common.opt import (
55
PathOutputType,
6+
confirm_insecure_tls,
67
opt_context,
8+
opt_insecure_tls_config,
79
opt_kubeconfig,
810
opt_namespace,
911
opt_nointeractive,
@@ -45,13 +47,15 @@ def import_res():
4547
@opt_namespace
4648
@opt_kubeconfig
4749
@opt_context
50+
@opt_insecure_tls_config
4851
@opt_output_path_only
4952
@opt_nointeractive
5053
async def import_client(
5154
name: str,
5255
namespace: str,
5356
kubeconfig: Optional[str],
5457
context: Optional[str],
58+
insecure_tls_config: bool,
5559
allow: Optional[str],
5660
unsafe: bool,
5761
out: Optional[str],
@@ -63,6 +67,7 @@ async def import_client(
6367
if out is None and ClientConfigV1Alpha1.exists(name):
6468
raise click.ClickException(f"A client with the name '{name}' already exists")
6569
try:
70+
confirm_insecure_tls(insecure_tls_config, nointeractive)
6671
async with ClientsV1Alpha1Api(namespace, kubeconfig, context) as api:
6772
if unsafe is False and allow is None and nointeractive is False:
6873
unsafe = click.confirm("Allow unsafe driver client imports?")
@@ -74,6 +79,7 @@ async def import_client(
7479
click.echo("Fetching client credentials from cluster")
7580
allow_drivers = allow.split(",") if allow is not None and len(allow) > 0 else []
7681
client_config = await api.get_client_config(name, allow=allow_drivers, unsafe=unsafe)
82+
client_config.tls.insecure = insecure_tls_config
7783
config_path = ClientConfigV1Alpha1.save(client_config, out)
7884
# If this is the only client config, set it as default
7985
if out is None and len(ClientConfigV1Alpha1.list()) == 1:
@@ -100,6 +106,7 @@ async def import_client(
100106
@opt_namespace
101107
@opt_kubeconfig
102108
@opt_context
109+
@opt_insecure_tls_config
103110
@opt_output_path_only
104111
@opt_nointeractive
105112
async def import_exporter(
@@ -108,6 +115,7 @@ async def import_exporter(
108115
out: Optional[str],
109116
kubeconfig: Optional[str],
110117
context: Optional[str],
118+
insecure_tls_config: bool,
111119
output: PathOutputType,
112120
nointeractive: bool,
113121
):
@@ -119,10 +127,12 @@ async def import_exporter(
119127
else:
120128
raise click.ClickException(f'An exporter with the name "{name}" already exists')
121129
try:
130+
confirm_insecure_tls(insecure_tls_config, nointeractive)
122131
async with ExportersV1Alpha1Api(namespace, kubeconfig, context) as api:
123132
if output is None:
124133
click.echo("Fetching exporter credentials from cluster")
125134
exporter_config = await api.get_exporter_config(name)
135+
exporter_config.tls.insecure = insecure_tls_config
126136
config_path = ExporterConfigV1Alpha1.save(exporter_config, out)
127137
if output is None:
128138
click.echo(f"Exporter configuration successfully saved to {config_path}")

0 commit comments

Comments
 (0)