Skip to content

Commit

Permalink
Dashboard: Use envoy instead of ingress
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mook-as committed Aug 19, 2024
1 parent 97e4c77 commit c420aff
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 12 deletions.
120 changes: 120 additions & 0 deletions pkg/rancher-desktop/assets/scripts/rancher-manager-envoy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# yaml-language-server: $schema=https://github.com/jcchavezs/envoy-config-schema/releases/download/v1.21.0/v3_Bootstrap.json
---
static_resources:
listeners:
- name: tls-termination
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9443
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: rancher_manager
route_config:
name: route
virtual_hosts:
- name: app
domains: [ "*" ]
routes:
- match: { prefix: / }
route:
cluster: rancher-manager
host_rewrite_literal: localhost
append_x_forwarded_host: true
request_headers_to_add:
- header: { key: X-Forwarded-Proto, value: https }
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- header: { key: X-Forwarded-Port, value: '443' }
append_action: OVERWRITE_IF_EXISTS_OR_ADD
- header: { key: X-Forwarded-For, value: '192.0.2.1' }
append_action: OVERWRITE_IF_EXISTS_OR_ADD
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificates:
# openssl req -x509 -newkey rsa:2048 -keyout key.pem -out crt.pem
# -days 36500 -nodes -subj '/CN=rancher-manager-https-termination'
# Per CA/B BR 6.1.5 RSA keys are a minimum of 2048 bits; and ECDSA
# keys must be ST P‐256, NIST P‐384 or NIST P‐521.
- certificate_chain:
inline_string: |
-----BEGIN CERTIFICATE-----
MIIDOzCCAiOgAwIBAgIUA4weh/2CMM0zwHuSIhkbaFEvqRMwDQYJKoZIhvcNAQEL
BQAwLDEqMCgGA1UEAwwhcmFuY2hlci1tYW5hZ2VyLWh0dHBzLXRlcm1pbmF0aW9u
MCAXDTI0MDgxNTIzNDI0OFoYDzIxMjQwNzIyMjM0MjQ4WjAsMSowKAYDVQQDDCFy
YW5jaGVyLW1hbmFnZXItaHR0cHMtdGVybWluYXRpb24wggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDbpo3Nvrvi6Ev5MGX1ukYh3Tuu03MHtzimGZs/0U+r
LJoVLBkWd4fUNit1wfvYSOJEdb1WMeU/IS36AzmTs4vkRVpilcow5LLklrmn2XJf
M7uvzUzBCzz6VnP7D0ltcD2u3VDplQv/doqm6p0vKE6CpYiaSjGq5ks6DPXaJZKO
2HAtDjuIYJq8Dg+BwnkHmFHD30vpl7+LmnZ7WTmJlg1cqSCHDLKeNrVbTD9ua6GD
4ImK+kLQQXPsvMM1QZXIg7mWslBLD9ucQosTSzCN9aVFqNnd3Nx2Ir5G0tc6ZwKg
cDawJyc3fYUQocNhKlJPa+eQl5u0quzCRsqRTTNlCV/HAgMBAAGjUzBRMB0GA1Ud
DgQWBBRlRLHhQ1GwgWJHglLSaLiw7gaPyjAfBgNVHSMEGDAWgBRlRLHhQ1GwgWJH
glLSaLiw7gaPyjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAr
SPAIOVGSuVa8z+Va9+J6fG+TrCx2pR6HUlayDpWfZ6LZXN4lIQ1Nrfnt2amwxbRA
95gxSnJyAXS2jLaLLuqR1fCKFE/xxH7TyVyShr3mUUyP7rt/iHlig9Io3lST9mbk
/4ovlHJEQcgn+5TEfwzDzq76arvaLqpMKQk7p0V2F/YCoEE0V6d9ZMmgfyTG3ayA
wh1oodQFKrA8vXyhbIUP+kM5KAxm0qxQaYNbZfXTkCw4CEGSVxDv8hY1S606QUdS
/YYG4HHEzdSVqeDsV1F6mD28TMZfnOpP6OFLxLhi2TOwwsWPRwoxwL0H+i7glWUS
682jYqxqLq+/OKsX+6Ul

Check failure on line 69 in pkg/rancher-desktop/assets/scripts/rancher-manager-envoy.yaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`OKs` is not a recognized word. (unrecognized-spelling)

Check failure on line 69 in pkg/rancher-desktop/assets/scripts/rancher-manager-envoy.yaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`Yqxq` is not a recognized word. (unrecognized-spelling)
-----END CERTIFICATE-----
private_key:
inline_string: |
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbpo3Nvrvi6Ev5
MGX1ukYh3Tuu03MHtzimGZs/0U+rLJoVLBkWd4fUNit1wfvYSOJEdb1WMeU/IS36
AzmTs4vkRVpilcow5LLklrmn2XJfM7uvzUzBCzz6VnP7D0ltcD2u3VDplQv/doqm
6p0vKE6CpYiaSjGq5ks6DPXaJZKO2HAtDjuIYJq8Dg+BwnkHmFHD30vpl7+LmnZ7
WTmJlg1cqSCHDLKeNrVbTD9ua6GD4ImK+kLQQXPsvMM1QZXIg7mWslBLD9ucQosT
SzCN9aVFqNnd3Nx2Ir5G0tc6ZwKgcDawJyc3fYUQocNhKlJPa+eQl5u0quzCRsqR
TTNlCV/HAgMBAAECggEAA038MC5AcWeBTRx3TD0jNPs5HKY9ws304jrcZRdnFXI0
V0E0l2vw9TZjbQAgI97k2JbU5GkXw91h7bMCuMAoyKRqebU7N4UZU+sYm/ffiqMi
ncB++SCMKFAIqqxONIFNzEW0I++EILHN4DkDaGQ42ipXZcrb+HBCjXsIb+HE1LVR
yBXdxpQV3JWqJrYiM1iXch5tuW/03Re68wMq2nfpe854vFcd5UoV1w9kRdsEhlNT
BHi7uO0+LtoB0CY+WSMq2Dpbp5DTL4WfiVtvbT7W1rGAE/lDjcW/3n/ed8TvRrhd
/EkuTQKiIOR2QCrCEtVVmRisl78SW4/1bqrMq841QQKBgQDtzTquacD9OX2oWTO6
p2EVgSYHVnOfQaM+bUlq2NbcVsvoN+8QCWgw9mR6OxxH7CJvDNQG5mELEku2SeJ4
8LYhIkFEAyY7QDa+lIysclqY6wtq1Vw40hMs+idTfm78ZkGgsUgr7luZxj2HYUhx
zsPE3XcgznWN5lVkheXu1v1p9wKBgQDsdbokoYP6zfsuddrW6qe2GsXFY3N/CvrX
zBWN4FIoRLUYbDuxA+91Cbac5JCt4o6AUphsSz+qxqj1gvvNjjFMzGe7S2xJjSqu
H3csLKwpje9HL55cO1llnb559kg9XAbwLdJd5bWdhfRLahIbST+me36Mcfqqggbz
H5hAPl7EsQKBgD8zjmcQgFRM1VLK8m6nUawvePX2SiCHh2VuElctbl19TBBZ3VW7
yk9JDQdXcnrDDZvKIwf6bsxMfobiOCjAgQdpXUNAOwcAWAxq2sByXBXMUmqAblRD
sQkBKzaLod+/Ja4Zr/7NCNdj0rKKboCg3XMTEThM5v1hvExNMgE6bnudAoGAQCh5
RzMj0ktNWf/UTvgAZWLCQpqHXfMmuKLBPmudHxv1XxkO4SrGMCVgjRVfRC7yp1LB
1LBeKAIbGfJeTBnGuqXDh4gha5uH9xLGjQ/Z7rR6NgBvoWrhCLdSVVlDpJJxt31X
VO7c5k7QSB4Rp6GqSYu8fHL4pob9R75M2zGRGSECgYEAjYzGEmXo+f2ezI+GHYHB
F8wWQhREOONC10MJ2ADj4FoPgbMghdfbpkHDTC0FFQqi4gCOLpU7h4H8/PDOl9vL
yXe6fabXFZrFrTa9IYO1ImWa6lkOWY4hO7DcKqWQzHFll93+Cs0STAhdSfEad0Fe
Sibf5N6AjHN4gWm/gCnn2nw=

Check failure on line 99 in pkg/rancher-desktop/assets/scripts/rancher-manager-envoy.yaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`Cnn` is not a recognized word. (unrecognized-spelling)

Check failure on line 99 in pkg/rancher-desktop/assets/scripts/rancher-manager-envoy.yaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`Sibf` is not a recognized word. (unrecognized-spelling)
-----END PRIVATE KEY-----
clusters:
- name: rancher-manager
type: STRICT_DNS
load_assignment:
cluster_name: rancher-manager
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: rancher-manager.cattle-system.svc
port_value: 80
health_checks:
- timeout: 1s
interval: 30s
unhealthy_threshold: 5
healthy_threshold: 1
http_health_check:
host: localhost
path: /healthz
55 changes: 53 additions & 2 deletions pkg/rancher-desktop/backend/k3sHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Architecture, VMExecutor } from './backend';

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

import type Electron from 'electron';
import mainEvents from '@pkg/main/mainEvents';

const KubeContextName = 'rancher-desktop';
const RancherPassword = 'password';
Expand Down Expand Up @@ -1260,6 +1262,9 @@ export default class K3sHelper extends events.EventEmitter {
postDelete: {
enabled: false,
},
ingresss: {

Check failure on line 1265 in pkg/rancher-desktop/backend/k3sHelper.ts

View workflow job for this annotation

GitHub Actions / Check Spelling

`ingresss` is not a recognized word. (unrecognized-spelling)
enabled: false,
},
extraEnv: [
{ name: 'CATTLE_FEATURES',
value: [
Expand All @@ -1275,6 +1280,47 @@ export default class K3sHelper extends events.EventEmitter {
}),
},
},
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: 'rancher-envoy',
namespace: 'cattle-system',
},
spec: {
replicas: 1,
selector: {
matchLabels: { app: 'rancher-envoy' },
},
template: {
metadata: { labels: { app: 'rancher-envoy' } },
spec: {
containers: [{
name: 'envoy',
image: 'envoyproxy/envoy:distroless-v1.31-latest',

Check failure on line 1300 in pkg/rancher-desktop/backend/k3sHelper.ts

View workflow job for this annotation

GitHub Actions / Check Spelling

`distroless` is not a recognized word. (unrecognized-spelling)

Check failure on line 1300 in pkg/rancher-desktop/backend/k3sHelper.ts

View workflow job for this annotation

GitHub Actions / Check Spelling

`envoyproxy` is not a recognized word. (unrecognized-spelling)
args: [
'--config-yaml', RANCHER_MANAGER_ENVOY_CONFIG
],
}],
}
},
}
},
{
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: 'rancher-envoy',
namespace: 'cattle-system',
},
spec: {
selector: { app: 'rancher-envoy' },
ports: [{
port: 443,
targetPort: 9443,
}],
}
},
]
promises.push(
vmx.copyFileIn(path.join(paths.resources, 'cert-manager.crds.yaml'), manifestFilename(MANIFEST_CERT_MANAGER_CRDS)),
Expand All @@ -1299,10 +1345,16 @@ export default class K3sHelper extends events.EventEmitter {
return;
}

const hostPort = await client.forwardPort('cattle-system', 'rancher-envoy', 9443, 0);
if (!hostPort) {
return;
}
mainEvents.emit('dashboard/port-changed', hostPort);

const timeout = AbortSignal.timeout(10 * 60 * 1_000);
while (!timeout.aborted) {
try {
const url = `https://localhost/dashboard/?setup=${ RancherPassword }`;
const url = `https://localhost:${ hostPort }/dashboard/?setup=${ RancherPassword }`;
const agent = new https.Agent({ rejectUnauthorized: false });
const resp = await fetch(url, { agent });

Expand Down Expand Up @@ -1332,7 +1384,6 @@ export default class K3sHelper extends events.EventEmitter {
await setSetting('first-login', 'false');
await setSetting('eula-agreed', (new Date).toISOString());
}

}

interface V1HelmChart {
Expand Down
6 changes: 5 additions & 1 deletion pkg/rancher-desktop/backend/kube/wsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export default class WSLKubernetesBackend extends events.EventEmitter implements
'install-k3s', version.raw, await this.vm.wslify(path.join(paths.cache, 'k3s')));

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

async stop() {
Expand Down
6 changes: 6 additions & 0 deletions pkg/rancher-desktop/main/mainEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ interface MainEventNames {
*/
'extensions/ui/uninstall'(id: string): void;

/**
* Emitted when the dashboard port has changed.
* @param port The port that the dashboard is now on.
*/
'dashboard/port-changed'(port: number): void;

/**
* Emitted on application quit, used to shut down any integrations. This
* requires feedback from the handler to know when all tasks are complete.
Expand Down
8 changes: 6 additions & 2 deletions pkg/rancher-desktop/main/networking/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import getWinCertificates from './win-ca';

import mainEvents from '@pkg/main/mainEvents';
import Logging from '@pkg/utils/logging';
import { getWindowName, windowMapping } from '@pkg/window';
import { getWindowName } from '@pkg/window';

const console = Logging.networking;

let dashboardPort = 0;

mainEvents.on('dashboard/port-changed', port => dashboardPort = port);

export default async function setupNetworking() {
const agentOptions = { ...https.globalAgent.options };

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

if (
windowName === 'main' &&
Expand Down
9 changes: 5 additions & 4 deletions pkg/rancher-desktop/preload/dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { ipcRenderer } from '@pkg/utils/ipcRenderer';

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

if (resp.status === 401) {
const token = await ipcRenderer.invoke('dashboard/get-csrf-token') ?? '';
const loginURL = 'https://localhost/v3-public/localProviders/local?action=login';
const loginURL = `https://localhost:${ dashboardPort }/v3-public/localProviders/local?action=login`;
const resp = await fetch(loginURL, {
headers: {
'Accept': "application/json",
Expand Down
2 changes: 1 addition & 1 deletion pkg/rancher-desktop/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import initExtensions from './extensions';

function init() {
initExtensions();
initDashboard();
initDashboard().catch(ex => console.error(ex));
}

try {
Expand Down
1 change: 1 addition & 0 deletions pkg/rancher-desktop/typings/electron-ipc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export interface IpcMainInvokeEvents {

// #region dashboard
'dashboard/get-csrf-token': () => string | null;
'dashboard/get-port': () => number;
// #endregion
}

Expand Down
9 changes: 8 additions & 1 deletion pkg/rancher-desktop/window/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import { createWindow, getWindow } from '.';
import paths from '@pkg/utils/paths';
import { getIpcMainProxy } from '@pkg/main/ipcMain';
import Logging from '@pkg/utils/logging';
import mainEvents from '@pkg/main/mainEvents';

const dashboardName = 'dashboard';
const dashboardURL = 'https://localhost/dashboard/c/local/explorer';
const console = Logging.dashboard;
const ipcMain = getIpcMainProxy(console);

let dashboardPort = 0;

mainEvents.on('dashboard/port-changed', port => dashboardPort = port);

ipcMain.removeHandler('dashboard/get-csrf-token');
ipcMain.handle('dashboard/get-csrf-token', async (event) => {
const webContents = event.sender;
Expand Down Expand Up @@ -37,8 +41,11 @@ ipcMain.handle('dashboard/get-csrf-token', async (event) => {
});
}
});
ipcMain.removeHandler('dashboard/get-port');
ipcMain.handle('dashboard/get-port', () => dashboardPort);

export function openDashboard() {
const dashboardURL = `https://localhost:${ dashboardPort }/dashboard/c/local/explorer`;
const window = createWindow('dashboard', dashboardURL, {
title: 'Rancher Dashboard',
width: 800,
Expand Down
6 changes: 5 additions & 1 deletion pkg/rancher-desktop/window/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import paths from '@pkg/utils/paths';
import { CommandOrControl, Shortcuts } from '@pkg/utils/shortcuts';
import { mainRoutes } from '@pkg/window/constants';
import { openPreferences } from '@pkg/window/preferences';
import mainEvents from '@pkg/main/mainEvents';

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

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

let dashboardPort = 0;
mainEvents.on('dashboard/port-changed', port => dashboardPort = port);

/**
* Open a given window; if it is already open, focus it.
* @param name The window identifier; this controls window re-use.
Expand All @@ -75,7 +79,7 @@ export function createWindow(name: string, url: string, options: Electron.Browse

function isInternalURL(url: string) {
if (name === 'dashboard') {
return url.startsWith('https://localhost/');
return url.startsWith(`https://localhost:${ dashboardPort }/`);
}
return url.startsWith(`${ webRoot }/`) || url.startsWith('x-rd-extension://');
}
Expand Down

0 comments on commit c420aff

Please sign in to comment.