Skip to content

Commit 71c1809

Browse files
committed
backend, frontend, electron: Allow kubeconfig cluster removal via flag
Introduces a new backend configuration flag --allow-kubeconfig-removal to control whether users can delete clusters sourced from their kubeconfig via the UI. - backend: Add flag and expose it in client config. - frontend: Conditionally enable delete option based on flag. - electron: Enable flag by default as desktop app already supports kubeconfig management. - makefile: Enable flag in dev mode.
1 parent a51ad40 commit 71c1809

File tree

8 files changed

+38
-10
lines changed

8 files changed

+38
-10
lines changed

Makefile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,10 @@ run-backend:
234234
@echo "**** Warning: Running with Helm and dynamic-clusters endpoints enabled. ****"
235235

236236
ifeq ($(UNIXSHELL),true)
237-
HEADLAMP_BACKEND_TOKEN=headlamp HEADLAMP_CONFIG_ENABLE_HELM=true HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true ./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
237+
HEADLAMP_BACKEND_TOKEN=headlamp HEADLAMP_CONFIG_ENABLE_HELM=true HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true ./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
238238
else
239239
@echo "**** Running on Windows without bash or zsh. ****"
240-
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
240+
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& set HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
241241
endif
242242

243243
run-dev:
@@ -251,10 +251,11 @@ ifeq ($(UNIXSHELL),true)
251251
HEADLAMP_CONFIG_METRICS_ENABLED=true \
252252
HEADLAMP_CONFIG_ENABLE_HELM=true \
253253
HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true \
254+
HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true \
254255
./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
255256
else
256257
@echo "**** Running on Windows without bash or zsh. ****"
257-
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_METRICS_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
258+
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_METRICS_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& set HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
258259
endif
259260

260261
run-backend-with-traces:
@@ -264,10 +265,11 @@ ifeq ($(UNIXSHELL),true)
264265
HEADLAMP_CONFIG_TRACING_ENABLED=true \
265266
HEADLAMP_CONFIG_ENABLE_HELM=true \
266267
HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true \
268+
HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true \
267269
./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
268270
else
269271
@echo "**** Running on Windows without bash or zsh. ****"
270-
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_TRACING_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
272+
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_TRACING_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& set HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
271273
endif
272274

273275
run-frontend:

app/electron/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ async function startServer(flags: string[] = []): Promise<ChildProcessWithoutNul
754754
// Enable the Helm and dynamic cluster endpoints
755755
process.env.HEADLAMP_CONFIG_ENABLE_HELM = 'true';
756756
process.env.HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS = 'true';
757+
process.env.HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL = 'true';
757758

758759
// Pass a token to the backend that can be used for auth on some routes
759760
process.env.HEADLAMP_BACKEND_TOKEN = backendToken;

backend/cmd/headlamp.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const (
9797
type clientConfig struct {
9898
Clusters []Cluster `json:"clusters"`
9999
IsDynamicClusterEnabled bool `json:"isDynamicClusterEnabled"`
100+
AllowKubeconfigRemoval bool `json:"allowKubeconfigRemoval"`
100101
}
101102

102103
type OauthConfig struct {
@@ -1749,7 +1750,11 @@ func parseClusterFromKubeConfig(kubeConfigs []string) ([]Cluster, []error) {
17491750
func (c *HeadlampConfig) getConfig(w http.ResponseWriter, r *http.Request) {
17501751
w.Header().Set("Content-Type", "application/json")
17511752

1752-
clientConfig := clientConfig{c.getClusters(), c.EnableDynamicClusters}
1753+
clientConfig := clientConfig{
1754+
Clusters: c.getClusters(),
1755+
IsDynamicClusterEnabled: c.EnableDynamicClusters,
1756+
AllowKubeconfigRemoval: c.AllowKubeconfigRemoval,
1757+
}
17531758

17541759
if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
17551760
logger.Log(logger.LevelError, nil, err, "encoding config")

backend/cmd/stateless.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ func (c *HeadlampConfig) parseKubeConfig(w http.ResponseWriter, r *http.Request)
178178
return
179179
}
180180

181-
clientConfig := clientConfig{contexts, c.EnableDynamicClusters}
181+
clientConfig := clientConfig{
182+
Clusters: contexts,
183+
IsDynamicClusterEnabled: c.EnableDynamicClusters,
184+
AllowKubeconfigRemoval: c.AllowKubeconfigRemoval,
185+
}
182186

183187
if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
184188
logger.Log(logger.LevelError, nil, err, "encoding config")

backend/pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type Config struct {
4545
CacheEnabled bool `koanf:"cache-enabled"`
4646
EnableHelm bool `koanf:"enable-helm"`
4747
EnableDynamicClusters bool `koanf:"enable-dynamic-clusters"`
48+
AllowKubeconfigRemoval bool `koanf:"allow-kubeconfig-removal"`
4849
ListenAddr string `koanf:"listen-addr"`
4950
WatchPluginsChanges bool `koanf:"watch-plugins-changes"`
5051
Port uint `koanf:"port"`
@@ -421,6 +422,7 @@ func addGeneralFlags(f *flag.FlagSet) {
421422
f.Bool("insecure-ssl", false, "Accept/Ignore all server SSL certificates")
422423
f.String("log-level", "info", "Set backend log verbosity. Options: debug, info (default), warn, error")
423424
f.Bool("enable-dynamic-clusters", false, "Enable dynamic clusters, which stores stateless clusters in the frontend.")
425+
f.Bool("allow-kubeconfig-removal", false, "Allow users to remove clusters from their kubeconfig via the UI")
424426
// Note: When running in-cluster and if not explicitly set, this flag defaults to false.
425427
f.Bool("watch-plugins-changes", true, "Reloads plugins when there are changes to them or their directory")
426428

backend/pkg/headlampconfig/headlampConfig.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ type HeadlampCFG struct {
4646
DevMode bool
4747
Insecure bool
4848
EnableHelm bool
49-
EnableDynamicClusters bool
49+
EnableDynamicClusters bool
50+
AllowKubeconfigRemoval bool
5051
WatchPluginsChanges bool
5152
Port uint
5253
KubeConfigPath string

frontend/src/components/App/Home/ClusterContextMenu.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export default function ClusterContextMenu({ cluster }: ClusterContextMenuProps)
5353
const dialogs = useTypedSelector(state => state.clusterProvider.dialogs);
5454
const menuItems = useTypedSelector(state => state.clusterProvider.menuItems);
5555
const isDynamicClusterEnabled = useTypedSelector(state => state.config.isDynamicClusterEnabled);
56+
const allowKubeconfigRemoval = useTypedSelector(state => state.config.allowKubeconfigRemoval);
5657

5758
const kubeconfigOrigin = cluster.meta_data?.origin?.kubeconfig;
5859
const deleteFromKubeconfig = cluster.meta_data?.source === 'kubeconfig';
@@ -155,9 +156,10 @@ export default function ClusterContextMenu({ cluster }: ClusterContextMenuProps)
155156
<ListItemText>{t('translation|Settings')}</ListItemText>
156157
</MenuItem>
157158
{(!menuItems || menuItems.length === 0) &&
158-
(helpers.isElectron() || isDynamicClusterEnabled) &&
159-
(cluster.meta_data?.source === 'dynamic_cluster' ||
160-
cluster.meta_data?.source === 'kubeconfig') && (
159+
((cluster.meta_data?.source === 'dynamic_cluster' &&
160+
(helpers.isElectron() || isDynamicClusterEnabled)) ||
161+
(cluster.meta_data?.source === 'kubeconfig' &&
162+
(helpers.isElectron() || allowKubeconfigRemoval))) && (
161163
<MenuItem
162164
onClick={() => {
163165
setOpenConfirmDialog('deleteDynamic');

frontend/src/redux/configSlice.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ export interface ConfigState {
4747
* When true, users can add and delete clusters dynamically.
4848
*/
4949
isDynamicClusterEnabled: boolean;
50+
/**
51+
* Whether users are allowed to remove clusters from their kubeconfig.
52+
* When true, the UI will show options to delete kubeconfig-sourced clusters.
53+
* Defaults to false to prevent accidental removal in company-deployed environments.
54+
*/
55+
allowKubeconfigRemoval: boolean;
5056
/**
5157
* Settings is a map of settings names to settings values.
5258
*/
@@ -77,6 +83,7 @@ export const initialState: ConfigState = {
7783
statelessClusters: null,
7884
allClusters: null,
7985
isDynamicClusterEnabled: false,
86+
allowKubeconfigRemoval: false,
8087
settings: {
8188
tableRowsPerPageOptions:
8289
storedSettings.tableRowsPerPageOptions || defaultTableRowsPerPageOptions,
@@ -100,12 +107,16 @@ const configSlice = createSlice({
100107
action: PayloadAction<{
101108
clusters: ConfigState['clusters'];
102109
isDynamicClusterEnabled?: boolean;
110+
allowKubeconfigRemoval?: boolean;
103111
}>
104112
) {
105113
state.clusters = action.payload.clusters;
106114
if (action.payload.isDynamicClusterEnabled !== undefined) {
107115
state.isDynamicClusterEnabled = action.payload.isDynamicClusterEnabled;
108116
}
117+
if (action.payload.allowKubeconfigRemoval !== undefined) {
118+
state.allowKubeconfigRemoval = action.payload.allowKubeconfigRemoval;
119+
}
109120
},
110121
/**
111122
* Save the config. To both the store, and localStorage.

0 commit comments

Comments
 (0)