Skip to content

Commit

Permalink
Merge pull request #12131 from richard-cox/2.10-vsphere-secret
Browse files Browse the repository at this point in the history
[2.10] vSphere Secrets Sync
  • Loading branch information
richard-cox authored Oct 10, 2024
2 parents b7a5578 + 6308e34 commit 9c7717c
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 28 deletions.
52 changes: 26 additions & 26 deletions shell/assets/translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,8 @@ addClusterMemberDialog:
title: Add Cluster Member

addonConfigConfirmation:
title: Add-On Config Reset
body: Changing the Kubernetes Version can reset the Add-On Config values. You should check that the values are as expected before continuing.
title: Add-On Reset
body: Changing the Kubernetes Version can reset Add-On values. You should check that the values are as expected before continuing.

addProjectMemberDialog:
title: Add Project Member
Expand Down Expand Up @@ -1194,38 +1194,38 @@ cluster:
<code>Cluster Management > Advanced > JWT Authentication</code>"
addonChart:
rancher-vsphere-cpi:
label: vSphere CPI
configuration: vSphere CPI Configuration
label: "Add-on: vSphere CPI"
configuration: vSphere CPI
rancher-vsphere-csi:
label: vSphere CSI
configuration: vSphere CSI Configuration
label: "Add-on: vSphere CSI"
configuration: vSphere CSI
rke2-calico:
label: Calico
configuration: Calico Configuration
label: "Add-on: Calico"
configuration: Calico
rke2-calico-crd:
label: Calico
configuration: Calico Configuration
label: "Add-on: Calico"
configuration: Calico
rke2-canal:
label: Canal
configuration: Canal Configuration
label: "Add-on: Canal"
configuration: Canal
rke2-cilium:
label: Cilium
configuration: Cilium Configuration
label: "Add-on: Cilium"
configuration: Cilium
rke2-coredns:
label: CoreDNS
configuration: CoreDNS Configuration
label: "Add-on: CoreDNS"
configuration: CoreDNS
rke2-ingress-nginx:
label: NGINX
configuration: NGINX Ingress Configuration
label: "Add-on: NGINX"
configuration: NGINX Ingress
rke2-kube-proxy:
label: Kube Proxy
configuration: Kube Proxy Configuration
label: "Add-on: Kube Proxy"
configuration: Kube Proxy
rke2-metrics-server:
label: Metrics Server
configuration: Metrics Server Configuration
label: "Add-on: Metrics Server"
configuration: Metrics Server
rke2-multus:
label: Multus
configuration: Multus Configuration
label: "Add-on: Multus"
configuration: Multus
agentEnvVars:
label: Agent Environment
detail: Add additional environment variables to the agent container. This is most commonly useful for configuring a HTTP proxy.
Expand All @@ -1241,7 +1241,7 @@ cluster:
label: Google
rancher-vsphere:
label: vSphere
note: '<b>Important:</b> Configure the vSphere Cloud Provider and Storage Provider options in the tabs on the left.'
note: '<b>Important:</b> Configure the vSphere Cloud Provider and Storage Provider options in the Add-on tabs.'
harvester:
label: Harvester
copyConfig: Copy KubeConfig to Clipboard
Expand Down Expand Up @@ -1758,7 +1758,7 @@ cluster:
serverOs:
label: OS
addOns:
dependencyBanner: Add-On Configurations can vary between Kubernetes versions. Changing the Kubernetes version may reset the values below.
dependencyBanner: Add-On Configuration can vary between Kubernetes versions. Changing the Kubernetes version may reset the values below.
additionalManifest:
title: Additional Manifest
tooltip: 'Additional Kubernetes Manifest YAML to be applied to the cluster on startup.'
Expand Down
11 changes: 11 additions & 0 deletions shell/edit/provisioning.cattle.io.cluster/rke2.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import AddOnConfig from '@shell/edit/provisioning.cattle.io.cluster/tabs/AddOnCo
import Advanced from '@shell/edit/provisioning.cattle.io.cluster/tabs/Advanced';
import ClusterAppearance from '@shell/components/form/ClusterAppearance';
import AddOnAdditionalManifest from '@shell/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest';
import VsphereUtils from '@shell/utils/v-sphere';
const HARVESTER = 'harvester';
const HARVESTER_CLOUD_PROVIDER = 'harvester-cloud-provider';
Expand Down Expand Up @@ -879,6 +880,8 @@ export default {
created() {
this.registerBeforeHook(this.saveMachinePools, 'save-machine-pools', 1);
this.registerBeforeHook(this.setRegistryConfig, 'set-registry-config');
this.registerBeforeHook(this.handleVsphereCpiSecret, 'sync-vsphere-cpi');
this.registerBeforeHook(this.handleVsphereCsiSecret, 'sync-vsphere-csi');
this.registerAfterHook(this.cleanupMachinePools, 'cleanup-machine-pools');
this.registerAfterHook(this.saveRoleBindings, 'save-role-bindings');
Expand All @@ -891,6 +894,14 @@ export default {
methods: {
set,
async handleVsphereCpiSecret() {
return VsphereUtils.handleVsphereCpiSecret(this);
},
async handleVsphereCsiSecret() {
return VsphereUtils.handleVsphereCsiSecret(this);
},
/**
* Initialize all the cluster specs
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default {
:as-object="true"
:editor-mode="mode === 'view' ? 'VIEW_CODE' : 'EDIT_CODE'"
:hide-preview-buttons="true"
@input="data => $emit('update-values', addonVersion.name, data)"
@update:value="$emit('update-values', addonVersion.name, $event)"
/>
<div class="spacer" />
</div>
Expand Down
2 changes: 1 addition & 1 deletion shell/utils/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function abbreviateClusterName(input) {

export function labelForAddon(store, name, configuration = true) {
const addon = camelToTitle(name.replace(/^(rke|rke2|rancher)-/, ''));
const fallback = `${ addon } ${ configuration ? 'Configuration' : '' }`;
const fallback = `${ configuration ? '' : 'Add-on: ' }${ addon }`;
const key = `cluster.addonChart."${ name }"${ configuration ? '.configuration' : '.label' }`;

return store.getters['i18n/withFallback'](key, null, fallback);
Expand Down
240 changes: 240 additions & 0 deletions shell/utils/v-sphere.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import merge from 'lodash/merge';
import { SECRET } from '@shell/config/types';

type Rke2Component = {
versionInfo: any;
userChartValues: any;
chartVersionKey: (chartName: string) => string;
value: any;
isEdit: boolean;
$store: any,
}

type SecretDetails = {
generateName: string,
upstreamClusterName: string,
upstreamNamespace: string,
downstreamName: string,
downstreamNamespace: string,
json?: object,
}
type Values = any;

type ChartValues = {
defaultValues: Values,
userValues: Values,
combined: Values,
};

const rootGenerateName = 'vsphere-secret-';

type SecretJson = any;

class VSphereUtils {
private async findSecret(
{ $store }: Rke2Component, {
generateName, upstreamClusterName, upstreamNamespace, downstreamName, downstreamNamespace
}: SecretDetails): Promise<SecretJson | undefined> {
// Fetch secrets in a specific namespace and partially matching the name to the generate name
const secrets = await $store.dispatch('management/request', { url: `/v1/${ SECRET }/${ upstreamNamespace }?filter=metadata.name=${ generateName }` });

// Filter by specific annotations
const applicableSecret = secrets.data?.filter((s: any) => {
return s.metadata.annotations['provisioning.cattle.io/sync-target-namespace'] === downstreamNamespace &&
s.metadata.annotations['provisioning.cattle.io/sync-target-name'] === downstreamName &&
s.metadata.annotations['rke.cattle.io/object-authorized-for-clusters'].includes(upstreamClusterName);
});

// If there's more than one the user should tidy up... as it'll cause mayhem with the actual sync from local --> downstream cluster
if (applicableSecret.length > 1) {
return Promise.reject(new Error(`Found multiple matching secrets (${ upstreamNamespace }/${ upstreamNamespace } for ${ upstreamClusterName }), this will cause synchronizing mishaps. Consider removing stale secrets from old clusters`));
}

return applicableSecret[0];
}

private async findOrCreateSecret(
rke2Component: Rke2Component,
{
generateName, upstreamClusterName, upstreamNamespace, downstreamName, downstreamNamespace, json
}: SecretDetails
) {
const { $store } = rke2Component;

const secretJson = await this.findSecret(rke2Component, {
generateName,
upstreamClusterName,
upstreamNamespace,
downstreamName,
downstreamNamespace
}) || json;

return await $store.dispatch('management/create', secretJson);
}

private findChartValues({
versionInfo,
userChartValues,
chartVersionKey,
}: Rke2Component, chartName: string): ChartValues | undefined {
const chartValues = versionInfo[chartName]?.values;

if (!chartValues) {
return;
}
const userValues = userChartValues[chartVersionKey(chartName)];

return {
defaultValues: chartValues,
userValues,
combined: merge({}, chartValues || {}, userValues || {})
};
}

/**
* Create upstream vsphere cpi secret to sync downstream
*/
async handleVsphereCpiSecret(rke2Component: Rke2Component) {
const generateName = `${ rootGenerateName }cpi-`;
const downstreamName = 'vsphere-cpi-creds';
const downstreamNamespace = 'kube-system';
const { value } = rke2Component;

// check values for cpi chart has 'use our method' checkbox
const { userValues, combined } = this.findChartValues(rke2Component, 'rancher-vsphere-cpi') || {};

if (!combined?.vCenter?.credentialsSecret?.generate) {
return;
}

// find values needed in cpi chart value - https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-cpi/questions.yaml#L16-L42
const { username, password, host } = combined.vCenter;

if (!username || !password || !host) {
throw new Error('vSphere CPI username, password and host are all required when generating a new secret');
}

// create secret as per https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-cpi/templates/secret.yaml
const upstreamClusterName = value.metadata.name;
const upstreamNamespace = value.metadata.namespace;
const secret = await this.findOrCreateSecret(rke2Component, {
generateName,
upstreamClusterName,
upstreamNamespace,
downstreamName,
downstreamNamespace,
json: {
type: SECRET,
metadata: {
namespace: upstreamNamespace,
generateName,
labels: {
'vsphere-cpi-infra': 'secret',
component: 'rancher-vsphere-cpi-cloud-controller-manager'
},
annotations: {
'provisioning.cattle.io/sync-target-namespace': downstreamNamespace,
'provisioning.cattle.io/sync-target-name': downstreamName,
'rke.cattle.io/object-authorized-for-clusters': upstreamClusterName,
'provisioning.cattle.io/sync-bootstrap': 'true'
}
},
}
});

secret.setData(`${ host }.username`, username);
secret.setData(`${ host }.password`, password);

await secret.save();

// reset cpi chart values
if (!userValues.vCenter.credentialsSecret) {
userValues.vCenter.credentialsSecret = {};
}
userValues.vCenter.credentialsSecret.generate = false;
userValues.vCenter.credentialsSecret.name = downstreamName;
userValues.vCenter.username = '';
userValues.vCenter.password = '';
}

/**
* Create upstream vsphere csi secret to sync downstream
*/
async handleVsphereCsiSecret(rke2Component: Rke2Component) {
const generateName = `${ rootGenerateName }csi-`;
const downstreamName = 'vsphere-csi-creds';
const downstreamNamespace = 'kube-system';
const { value } = rke2Component;

// check values for cpi chart has 'use our method' checkbox
const { userValues, combined } = this.findChartValues(rke2Component, 'rancher-vsphere-csi') || {};

if (!combined?.vCenter?.configSecret?.generate) {
return;
}

// find values needed in cpi chart value - https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-csi/questions.yaml#L1-L36
const {
username, password, host, datacenters, port, insecureFlag
} = combined.vCenter;

if (!username || !password || !host || !datacenters) {
throw new Error('vSphere CSI username, password, host and datacenters are all required when generating a new secret');
}

// This is a copy of https://github.com/rancher/vsphere-charts/blob/a5c99d716df960dc50cf417d9ecffad6b55ca0ad/charts/rancher-vsphere-csi/values.yaml#L12-L21
// Which makes it's way into the secret via https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-csi/templates/secret.yaml#L8
let configTemplateString = ' |\n [Global]\n cluster-id = {{ required \".Values.vCenter.clusterId must be provided\" (default .Values.vCenter.clusterId .Values.global.cattle.clusterId) | quote }}\n user = {{ .Values.vCenter.username | quote }}\n password = {{ .Values.vCenter.password | quote }}\n port = {{ .Values.vCenter.port | quote }}\n insecure-flag = {{ .Values.vCenter.insecureFlag | quote }}\n\n [VirtualCenter {{ .Values.vCenter.host | quote }}]\n datacenters = {{ .Values.vCenter.datacenters | quote }}';

configTemplateString = configTemplateString.replace('{{ required \".Values.vCenter.clusterId must be provided\" (default .Values.vCenter.clusterId .Values.global.cattle.clusterId) | quote }}', `"{{clusterId}}"`);
configTemplateString = configTemplateString.replace('{{ .Values.vCenter.username | quote }}', `"${ username }"`);
configTemplateString = configTemplateString.replace('{{ .Values.vCenter.password | quote }}', `"${ password }"`);
configTemplateString = configTemplateString.replace('{{ .Values.vCenter.port | quote }}', `"${ port }"`);
configTemplateString = configTemplateString.replace('{{ .Values.vCenter.insecureFlag | quote }}', `"${ insecureFlag }"`);
configTemplateString = configTemplateString.replace('{{ .Values.vCenter.host | quote }}', `"${ host }"`);
configTemplateString = configTemplateString.replace('{{ .Values.vCenter.datacenters | quote }}', `"${ datacenters }"`);
// create secret as per https://github.com/rancher/vsphere-charts/blob/main/charts/rancher-vsphere-csi/templates/secret.yaml
const upstreamClusterName = value.metadata.name;
const upstreamNamespace = value.metadata.namespace;

const secret = await this.findOrCreateSecret(rke2Component, {
generateName,
upstreamClusterName,
upstreamNamespace,
downstreamName,
downstreamNamespace,
json: {
type: SECRET,
metadata: {
namespace: upstreamNamespace,
generateName,
annotations: {
'provisioning.cattle.io/sync-target-namespace': downstreamNamespace,
'provisioning.cattle.io/sync-target-name': downstreamName,
'rke.cattle.io/object-authorized-for-clusters': upstreamClusterName,
'provisioning.cattle.io/sync-bootstrap': 'true'
}
},
}
});

secret.setData(`csi-vsphere.conf`, configTemplateString);

await secret.save();

// reset csi chart values
if (!userValues.vCenter.configSecret) {
userValues.vCenter.configSecret = {};
}
userValues.vCenter.configSecret.generate = false;
userValues.vCenter.configSecret.name = downstreamName;
userValues.vCenter.username = '';
userValues.vCenter.password = '';
userValues.vCenter.host = '';
userValues.vCenter.datacenters = '';
}
}

const utils = new VSphereUtils();

export default utils;

0 comments on commit 9c7717c

Please sign in to comment.