Skip to content

Commit c420aff

Browse files
committed
Dashboard: Use envoy instead of ingress
Since ingress doesn't currently work on Windows, use envoy to do SSL termination and do Kubernetes-level service port forwarding instead. This also means it will work without traefik.
1 parent 97e4c77 commit c420aff

File tree

10 files changed

+210
-12
lines changed

10 files changed

+210
-12
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# yaml-language-server: $schema=https://github.com/jcchavezs/envoy-config-schema/releases/download/v1.21.0/v3_Bootstrap.json
2+
---
3+
static_resources:
4+
listeners:
5+
- name: tls-termination
6+
address:
7+
socket_address:
8+
protocol: TCP
9+
address: 0.0.0.0
10+
port_value: 9443
11+
filter_chains:
12+
- filters:
13+
- name: envoy.filters.network.http_connection_manager
14+
typed_config:
15+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
16+
stat_prefix: rancher_manager
17+
route_config:
18+
name: route
19+
virtual_hosts:
20+
- name: app
21+
domains: [ "*" ]
22+
routes:
23+
- match: { prefix: / }
24+
route:
25+
cluster: rancher-manager
26+
host_rewrite_literal: localhost
27+
append_x_forwarded_host: true
28+
request_headers_to_add:
29+
- header: { key: X-Forwarded-Proto, value: https }
30+
append_action: OVERWRITE_IF_EXISTS_OR_ADD
31+
- header: { key: X-Forwarded-Port, value: '443' }
32+
append_action: OVERWRITE_IF_EXISTS_OR_ADD
33+
- header: { key: X-Forwarded-For, value: '192.0.2.1' }
34+
append_action: OVERWRITE_IF_EXISTS_OR_ADD
35+
http_filters:
36+
- name: envoy.filters.http.router
37+
typed_config:
38+
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
39+
transport_socket:
40+
name: envoy.transport_sockets.tls
41+
typed_config:
42+
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
43+
common_tls_context:
44+
tls_certificates:
45+
# openssl req -x509 -newkey rsa:2048 -keyout key.pem -out crt.pem
46+
# -days 36500 -nodes -subj '/CN=rancher-manager-https-termination'
47+
# Per CA/B BR 6.1.5 RSA keys are a minimum of 2048 bits; and ECDSA
48+
# keys must be ST P‐256, NIST P‐384 or NIST P‐521.
49+
- certificate_chain:
50+
inline_string: |
51+
-----BEGIN CERTIFICATE-----
52+
MIIDOzCCAiOgAwIBAgIUA4weh/2CMM0zwHuSIhkbaFEvqRMwDQYJKoZIhvcNAQEL
53+
BQAwLDEqMCgGA1UEAwwhcmFuY2hlci1tYW5hZ2VyLWh0dHBzLXRlcm1pbmF0aW9u
54+
MCAXDTI0MDgxNTIzNDI0OFoYDzIxMjQwNzIyMjM0MjQ4WjAsMSowKAYDVQQDDCFy
55+
YW5jaGVyLW1hbmFnZXItaHR0cHMtdGVybWluYXRpb24wggEiMA0GCSqGSIb3DQEB
56+
AQUAA4IBDwAwggEKAoIBAQDbpo3Nvrvi6Ev5MGX1ukYh3Tuu03MHtzimGZs/0U+r
57+
LJoVLBkWd4fUNit1wfvYSOJEdb1WMeU/IS36AzmTs4vkRVpilcow5LLklrmn2XJf
58+
M7uvzUzBCzz6VnP7D0ltcD2u3VDplQv/doqm6p0vKE6CpYiaSjGq5ks6DPXaJZKO
59+
2HAtDjuIYJq8Dg+BwnkHmFHD30vpl7+LmnZ7WTmJlg1cqSCHDLKeNrVbTD9ua6GD
60+
4ImK+kLQQXPsvMM1QZXIg7mWslBLD9ucQosTSzCN9aVFqNnd3Nx2Ir5G0tc6ZwKg
61+
cDawJyc3fYUQocNhKlJPa+eQl5u0quzCRsqRTTNlCV/HAgMBAAGjUzBRMB0GA1Ud
62+
DgQWBBRlRLHhQ1GwgWJHglLSaLiw7gaPyjAfBgNVHSMEGDAWgBRlRLHhQ1GwgWJH
63+
glLSaLiw7gaPyjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAr
64+
SPAIOVGSuVa8z+Va9+J6fG+TrCx2pR6HUlayDpWfZ6LZXN4lIQ1Nrfnt2amwxbRA
65+
95gxSnJyAXS2jLaLLuqR1fCKFE/xxH7TyVyShr3mUUyP7rt/iHlig9Io3lST9mbk
66+
/4ovlHJEQcgn+5TEfwzDzq76arvaLqpMKQk7p0V2F/YCoEE0V6d9ZMmgfyTG3ayA
67+
wh1oodQFKrA8vXyhbIUP+kM5KAxm0qxQaYNbZfXTkCw4CEGSVxDv8hY1S606QUdS
68+
/YYG4HHEzdSVqeDsV1F6mD28TMZfnOpP6OFLxLhi2TOwwsWPRwoxwL0H+i7glWUS
69+
682jYqxqLq+/OKsX+6Ul
70+
-----END CERTIFICATE-----
71+
private_key:
72+
inline_string: |
73+
-----BEGIN PRIVATE KEY-----
74+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbpo3Nvrvi6Ev5
75+
MGX1ukYh3Tuu03MHtzimGZs/0U+rLJoVLBkWd4fUNit1wfvYSOJEdb1WMeU/IS36
76+
AzmTs4vkRVpilcow5LLklrmn2XJfM7uvzUzBCzz6VnP7D0ltcD2u3VDplQv/doqm
77+
6p0vKE6CpYiaSjGq5ks6DPXaJZKO2HAtDjuIYJq8Dg+BwnkHmFHD30vpl7+LmnZ7
78+
WTmJlg1cqSCHDLKeNrVbTD9ua6GD4ImK+kLQQXPsvMM1QZXIg7mWslBLD9ucQosT
79+
SzCN9aVFqNnd3Nx2Ir5G0tc6ZwKgcDawJyc3fYUQocNhKlJPa+eQl5u0quzCRsqR
80+
TTNlCV/HAgMBAAECggEAA038MC5AcWeBTRx3TD0jNPs5HKY9ws304jrcZRdnFXI0
81+
V0E0l2vw9TZjbQAgI97k2JbU5GkXw91h7bMCuMAoyKRqebU7N4UZU+sYm/ffiqMi
82+
ncB++SCMKFAIqqxONIFNzEW0I++EILHN4DkDaGQ42ipXZcrb+HBCjXsIb+HE1LVR
83+
yBXdxpQV3JWqJrYiM1iXch5tuW/03Re68wMq2nfpe854vFcd5UoV1w9kRdsEhlNT
84+
BHi7uO0+LtoB0CY+WSMq2Dpbp5DTL4WfiVtvbT7W1rGAE/lDjcW/3n/ed8TvRrhd
85+
/EkuTQKiIOR2QCrCEtVVmRisl78SW4/1bqrMq841QQKBgQDtzTquacD9OX2oWTO6
86+
p2EVgSYHVnOfQaM+bUlq2NbcVsvoN+8QCWgw9mR6OxxH7CJvDNQG5mELEku2SeJ4
87+
8LYhIkFEAyY7QDa+lIysclqY6wtq1Vw40hMs+idTfm78ZkGgsUgr7luZxj2HYUhx
88+
zsPE3XcgznWN5lVkheXu1v1p9wKBgQDsdbokoYP6zfsuddrW6qe2GsXFY3N/CvrX
89+
zBWN4FIoRLUYbDuxA+91Cbac5JCt4o6AUphsSz+qxqj1gvvNjjFMzGe7S2xJjSqu
90+
H3csLKwpje9HL55cO1llnb559kg9XAbwLdJd5bWdhfRLahIbST+me36Mcfqqggbz
91+
H5hAPl7EsQKBgD8zjmcQgFRM1VLK8m6nUawvePX2SiCHh2VuElctbl19TBBZ3VW7
92+
yk9JDQdXcnrDDZvKIwf6bsxMfobiOCjAgQdpXUNAOwcAWAxq2sByXBXMUmqAblRD
93+
sQkBKzaLod+/Ja4Zr/7NCNdj0rKKboCg3XMTEThM5v1hvExNMgE6bnudAoGAQCh5
94+
RzMj0ktNWf/UTvgAZWLCQpqHXfMmuKLBPmudHxv1XxkO4SrGMCVgjRVfRC7yp1LB
95+
1LBeKAIbGfJeTBnGuqXDh4gha5uH9xLGjQ/Z7rR6NgBvoWrhCLdSVVlDpJJxt31X
96+
VO7c5k7QSB4Rp6GqSYu8fHL4pob9R75M2zGRGSECgYEAjYzGEmXo+f2ezI+GHYHB
97+
F8wWQhREOONC10MJ2ADj4FoPgbMghdfbpkHDTC0FFQqi4gCOLpU7h4H8/PDOl9vL
98+
yXe6fabXFZrFrTa9IYO1ImWa6lkOWY4hO7DcKqWQzHFll93+Cs0STAhdSfEad0Fe
99+
Sibf5N6AjHN4gWm/gCnn2nw=
100+
-----END PRIVATE KEY-----
101+
clusters:
102+
- name: rancher-manager
103+
type: STRICT_DNS
104+
load_assignment:
105+
cluster_name: rancher-manager
106+
endpoints:
107+
- lb_endpoints:
108+
- endpoint:
109+
address:
110+
socket_address:
111+
address: rancher-manager.cattle-system.svc
112+
port_value: 80
113+
health_checks:
114+
- timeout: 1s
115+
interval: 30s
116+
unhealthy_threshold: 5
117+
healthy_threshold: 1
118+
http_health_check:
119+
host: localhost
120+
path: /healthz

pkg/rancher-desktop/backend/k3sHelper.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { Architecture, VMExecutor } from './backend';
2222

2323
import CERT_MANAGER from '@pkg/assets/scripts/cert-manager.yaml';
2424
import SPIN_OPERATOR from '@pkg/assets/scripts/spin-operator.yaml';
25+
import RANCHER_MANAGER_ENVOY_CONFIG from '@pkg/assets/scripts/rancher-manager-envoy.yaml';
2526
import * as K8s from '@pkg/backend/k8s';
2627
import { KubeClient } from '@pkg/backend/kube/client';
2728
import { loadFromString, exportConfig } from '@pkg/backend/kubeconfig';
@@ -42,6 +43,7 @@ import { showMessageBox } from '@pkg/window';
4243
import DEPENDENCY_VERSIONS from '@pkg/assets/dependencies.yaml';
4344

4445
import type Electron from 'electron';
46+
import mainEvents from '@pkg/main/mainEvents';
4547

4648
const KubeContextName = 'rancher-desktop';
4749
const RancherPassword = 'password';
@@ -1260,6 +1262,9 @@ export default class K3sHelper extends events.EventEmitter {
12601262
postDelete: {
12611263
enabled: false,
12621264
},
1265+
ingresss: {
1266+
enabled: false,
1267+
},
12631268
extraEnv: [
12641269
{ name: 'CATTLE_FEATURES',
12651270
value: [
@@ -1275,6 +1280,47 @@ export default class K3sHelper extends events.EventEmitter {
12751280
}),
12761281
},
12771282
},
1283+
{
1284+
apiVersion: 'apps/v1',
1285+
kind: 'Deployment',
1286+
metadata: {
1287+
name: 'rancher-envoy',
1288+
namespace: 'cattle-system',
1289+
},
1290+
spec: {
1291+
replicas: 1,
1292+
selector: {
1293+
matchLabels: { app: 'rancher-envoy' },
1294+
},
1295+
template: {
1296+
metadata: { labels: { app: 'rancher-envoy' } },
1297+
spec: {
1298+
containers: [{
1299+
name: 'envoy',
1300+
image: 'envoyproxy/envoy:distroless-v1.31-latest',
1301+
args: [
1302+
'--config-yaml', RANCHER_MANAGER_ENVOY_CONFIG
1303+
],
1304+
}],
1305+
}
1306+
},
1307+
}
1308+
},
1309+
{
1310+
apiVersion: 'v1',
1311+
kind: 'Service',
1312+
metadata: {
1313+
name: 'rancher-envoy',
1314+
namespace: 'cattle-system',
1315+
},
1316+
spec: {
1317+
selector: { app: 'rancher-envoy' },
1318+
ports: [{
1319+
port: 443,
1320+
targetPort: 9443,
1321+
}],
1322+
}
1323+
},
12781324
]
12791325
promises.push(
12801326
vmx.copyFileIn(path.join(paths.resources, 'cert-manager.crds.yaml'), manifestFilename(MANIFEST_CERT_MANAGER_CRDS)),
@@ -1299,10 +1345,16 @@ export default class K3sHelper extends events.EventEmitter {
12991345
return;
13001346
}
13011347

1348+
const hostPort = await client.forwardPort('cattle-system', 'rancher-envoy', 9443, 0);
1349+
if (!hostPort) {
1350+
return;
1351+
}
1352+
mainEvents.emit('dashboard/port-changed', hostPort);
1353+
13021354
const timeout = AbortSignal.timeout(10 * 60 * 1_000);
13031355
while (!timeout.aborted) {
13041356
try {
1305-
const url = `https://localhost/dashboard/?setup=${ RancherPassword }`;
1357+
const url = `https://localhost:${ hostPort }/dashboard/?setup=${ RancherPassword }`;
13061358
const agent = new https.Agent({ rejectUnauthorized: false });
13071359
const resp = await fetch(url, { agent });
13081360

@@ -1332,7 +1384,6 @@ export default class K3sHelper extends events.EventEmitter {
13321384
await setSetting('first-login', 'false');
13331385
await setSetting('eula-agreed', (new Date).toISOString());
13341386
}
1335-
13361387
}
13371388

13381389
interface V1HelmChart {

pkg/rancher-desktop/backend/kube/wsl.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export default class WSLKubernetesBackend extends events.EventEmitter implements
177177
'install-k3s', version.raw, await this.vm.wslify(path.join(paths.cache, 'k3s')));
178178

179179
const promises: Promise<void>[] = [];
180-
promises.push(BackendHelper.configureKubeResources(this.vm,
180+
promises.push(K3sHelper.configureKubeResources(this.vm,
181181
config.experimental?.containerEngine?.webAssembly?.enabled &&
182182
!!config.experimental?.kubernetes?.options?.spinkube));
183183
if (config.experimental?.containerEngine?.webAssembly?.enabled) {
@@ -282,6 +282,10 @@ export default class WSLKubernetesBackend extends events.EventEmitter implements
282282
'Skipping node checks, flannel is disabled',
283283
100, Promise.resolve({}));
284284
}
285+
await this.progressTracker.action('Finishing Kubernetes Startup', 100,
286+
this.client?.getActivePod('kube-system', 'kube-dns'));
287+
await this.progressTracker.action('Setting up Rancher Dashboard', 100,
288+
K3sHelper.setupRancherManager(this.client));
285289
}
286290

287291
async stop() {

pkg/rancher-desktop/main/mainEvents.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ interface MainEventNames {
113113
*/
114114
'extensions/ui/uninstall'(id: string): void;
115115

116+
/**
117+
* Emitted when the dashboard port has changed.
118+
* @param port The port that the dashboard is now on.
119+
*/
120+
'dashboard/port-changed'(port: number): void;
121+
116122
/**
117123
* Emitted on application quit, used to shut down any integrations. This
118124
* requires feedback from the handler to know when all tasks are complete.

pkg/rancher-desktop/main/networking/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ import getWinCertificates from './win-ca';
1414

1515
import mainEvents from '@pkg/main/mainEvents';
1616
import Logging from '@pkg/utils/logging';
17-
import { getWindowName, windowMapping } from '@pkg/window';
17+
import { getWindowName } from '@pkg/window';
1818

1919
const console = Logging.networking;
2020

21+
let dashboardPort = 0;
22+
23+
mainEvents.on('dashboard/port-changed', port => dashboardPort = port);
24+
2125
export default async function setupNetworking() {
2226
const agentOptions = { ...https.globalAgent.options };
2327

@@ -45,7 +49,7 @@ export default async function setupNetworking() {
4549
Electron.app.on('certificate-error', async(event, webContents, url, error, certificate, callback) => {
4650
const windowName = getWindowName(webContents);
4751
const pluginDevUrls = [`https://localhost:8888`, `wss://localhost:8888`];
48-
const dashboardUrls = ['https://localhost/', 'wss://localhost/'];
52+
const dashboardUrls = [`https://localhost:${ dashboardPort }/`, `wss://localhost:${ dashboardPort }/`];
4953

5054
if (
5155
windowName === 'main' &&

pkg/rancher-desktop/preload/dashboard.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { ipcRenderer } from '@pkg/utils/ipcRenderer';
22

3-
export default function initDashboard(): void {
4-
if (!document.location.href.startsWith('https://localhost/dashboard/')) {
3+
export default async function initDashboard(): Promise<void> {
4+
const dashboardPort = await ipcRenderer.invoke('dashboard/get-port');
5+
if (!document.location.href.startsWith(`https://localhost:${ dashboardPort }/dashboard/`)) {
56
return;
67
}
78
// Navigation API is only available in Chrome-derived browsers like Electron.
89
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation
910
(window as any).navigation.addEventListener('navigate', async function onNavigate() {
10-
const resp = await fetch('https://localhost/v3/users?me=true');
11+
const resp = await fetch(`https://localhost:${ dashboardPort }/v3/users?me=true`);
1112
let loginSuccessful = false;
1213

1314
if (resp.status === 401) {
1415
const token = await ipcRenderer.invoke('dashboard/get-csrf-token') ?? '';
15-
const loginURL = 'https://localhost/v3-public/localProviders/local?action=login';
16+
const loginURL = `https://localhost:${ dashboardPort }/v3-public/localProviders/local?action=login`;
1617
const resp = await fetch(loginURL, {
1718
headers: {
1819
'Accept': "application/json",

pkg/rancher-desktop/preload/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import initExtensions from './extensions';
33

44
function init() {
55
initExtensions();
6-
initDashboard();
6+
initDashboard().catch(ex => console.error(ex));
77
}
88

99
try {

pkg/rancher-desktop/typings/electron-ipc.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export interface IpcMainInvokeEvents {
154154

155155
// #region dashboard
156156
'dashboard/get-csrf-token': () => string | null;
157+
'dashboard/get-port': () => number;
157158
// #endregion
158159
}
159160

pkg/rancher-desktop/window/dashboard.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ import { createWindow, getWindow } from '.';
33
import paths from '@pkg/utils/paths';
44
import { getIpcMainProxy } from '@pkg/main/ipcMain';
55
import Logging from '@pkg/utils/logging';
6+
import mainEvents from '@pkg/main/mainEvents';
67

78
const dashboardName = 'dashboard';
8-
const dashboardURL = 'https://localhost/dashboard/c/local/explorer';
99
const console = Logging.dashboard;
1010
const ipcMain = getIpcMainProxy(console);
1111

12+
let dashboardPort = 0;
13+
14+
mainEvents.on('dashboard/port-changed', port => dashboardPort = port);
15+
1216
ipcMain.removeHandler('dashboard/get-csrf-token');
1317
ipcMain.handle('dashboard/get-csrf-token', async (event) => {
1418
const webContents = event.sender;
@@ -37,8 +41,11 @@ ipcMain.handle('dashboard/get-csrf-token', async (event) => {
3741
});
3842
}
3943
});
44+
ipcMain.removeHandler('dashboard/get-port');
45+
ipcMain.handle('dashboard/get-port', () => dashboardPort);
4046

4147
export function openDashboard() {
48+
const dashboardURL = `https://localhost:${ dashboardPort }/dashboard/c/local/explorer`;
4249
const window = createWindow('dashboard', dashboardURL, {
4350
title: 'Rancher Dashboard',
4451
width: 800,

pkg/rancher-desktop/window/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import paths from '@pkg/utils/paths';
1414
import { CommandOrControl, Shortcuts } from '@pkg/utils/shortcuts';
1515
import { mainRoutes } from '@pkg/window/constants';
1616
import { openPreferences } from '@pkg/window/preferences';
17+
import mainEvents from '@pkg/main/mainEvents';
1718

1819
const console = Logging[`window_${ process.type || 'unknown' }`];
1920

@@ -60,6 +61,9 @@ export function getWindowName(webContents: Electron.WebContents): string | null
6061
return name;
6162
}
6263

64+
let dashboardPort = 0;
65+
mainEvents.on('dashboard/port-changed', port => dashboardPort = port);
66+
6367
/**
6468
* Open a given window; if it is already open, focus it.
6569
* @param name The window identifier; this controls window re-use.
@@ -75,7 +79,7 @@ export function createWindow(name: string, url: string, options: Electron.Browse
7579

7680
function isInternalURL(url: string) {
7781
if (name === 'dashboard') {
78-
return url.startsWith('https://localhost/');
82+
return url.startsWith(`https://localhost:${ dashboardPort }/`);
7983
}
8084
return url.startsWith(`${ webRoot }/`) || url.startsWith('x-rd-extension://');
8185
}

0 commit comments

Comments
 (0)