From a72d85c9e3ad6927a690bf6c8f0b0f9328315d68 Mon Sep 17 00:00:00 2001 From: otomi-admin <63190600+ferruhcihan@users.noreply.github.com> Date: Thu, 28 May 2026 08:15:22 +0200 Subject: [PATCH 1/5] feat: team-specific secret management with sealed secrets --- src/otomi-stack.ts | 187 ++++++++++++++++++++++++++++++++- src/utils/sealedSecretUtils.ts | 12 ++- 2 files changed, 195 insertions(+), 4 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 4d0870e84..a03cd113b 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -927,6 +927,166 @@ export default class OtomiStack { return settingsResponse } + /** + * Create or update platform-managed SealedSecrets in a team namespace. + * These replace ExternalSecrets that previously used core-secrets-store to read + * platform secrets — preventing teams from stealing other teams' or platform secrets. + * + * @param teamId - Team identifier (without 'team-' prefix) + * @param spec - Current team spec (used to check which features are enabled) + * @param teamPassword - Optional plaintext team password (only needed during team creation, + * before the SealedSecret is applied to K8s) + */ + private async createTeamNamespaceSealedSecrets( + teamId: string, + spec: AplTeamSettingsRequest['spec'], + teamPassword?: string, + ): Promise { + const teamNs = `team-${teamId}` + const grafanaEnabled = spec?.managedMonitoring?.grafana ?? false + const alertmanagerEnabled = spec?.managedMonitoring?.alertmanager ?? false + + if (grafanaEnabled) { + // team--grafana-admin: admin credentials for team's Grafana instance + let password = teamPassword + if (!password) { + const teamSecrets = await getSecretValues(`team-${teamId}-settings-secrets`, APL_SECRETS_NAMESPACE) + password = teamSecrets?.settings_password + } + if (password) { + const yamlContent = await createPlatformSealedSecretManifest(`${teamNs}-grafana-admin`, teamNs, { + 'admin-user': teamId, + 'admin-password': password, + }) + await this.git.writeTextFile( + getNamespaceSealedSecretsValuesFilePath(teamNs, `${teamNs}-grafana-admin`), + yamlContent, + ) + } + + // grafana-oidc-secret: Keycloak OIDC credentials for Grafana SSO + const keycloakSecrets = await getSecretValues('keycloak-secrets', APL_SECRETS_NAMESPACE) + const clientSecret = keycloakSecrets?.idp_clientSecret + const clientId = (this.getApp('keycloak').values as Record)?.idp?.clientID ?? '' + if (clientSecret) { + const yamlContent = await createPlatformSealedSecretManifest('grafana-oidc-secret', teamNs, { + client_id: clientId, + client_secret: clientSecret, + }) + await this.git.writeTextFile( + getNamespaceSealedSecretsValuesFilePath(teamNs, 'grafana-oidc-secret'), + yamlContent, + ) + } + + // grafana-loki-datasource-secret: Loki admin password for Grafana datasource + const lokiSecrets = await getSecretValues('loki-secrets', APL_SECRETS_NAMESPACE) + const lokiPassword = lokiSecrets?.adminPassword + if (lokiPassword) { + const yamlContent = await createPlatformSealedSecretManifest('grafana-loki-datasource-secret', teamNs, { + password: lokiPassword, + }) + await this.git.writeTextFile( + getNamespaceSealedSecretsValuesFilePath(teamNs, 'grafana-loki-datasource-secret'), + yamlContent, + ) + } + } + + if (alertmanagerEnabled) { + // alertmanager-credentials: notification channel credentials for team Alertmanager + const { alerts } = await this.getSettings(['alerts']) + const receivers = (spec?.alerts?.receivers ?? (alerts as Record)?.receivers ?? ['slack']) as string[] + const hasReceivers = !receivers.includes('none') + + if (hasReceivers) { + const alertData: Record = {} + + if (receivers.includes('slack')) { + const alertSecrets = await getSecretValues('alerts-secrets', APL_SECRETS_NAMESPACE) + if (alertSecrets?.slack_url) alertData.slackUrl = alertSecrets.slack_url + } + if (receivers.includes('email')) { + const smtpSecrets = await getSecretValues('smtp-secrets', APL_SECRETS_NAMESPACE) + if (smtpSecrets?.auth_password) alertData.smtpAuthPassword = smtpSecrets.auth_password + if (smtpSecrets?.auth_secret) alertData.smtpAuthSecret = smtpSecrets.auth_secret + } + if (receivers.includes('opsgenie')) { + // Legacy receiver — kept for backwards compatibility + const alertSecrets = await getSecretValues('alerts-secrets', APL_SECRETS_NAMESPACE) + if (alertSecrets?.opsgenie_apiKey) alertData.opsgenieApiKey = alertSecrets.opsgenie_apiKey + } + + if (Object.keys(alertData).length > 0) { + const yamlContent = await createPlatformSealedSecretManifest('alertmanager-credentials', teamNs, alertData) + await this.git.writeTextFile( + getNamespaceSealedSecretsValuesFilePath(teamNs, 'alertmanager-credentials'), + yamlContent, + ) + } + } + } + + // otomi-pullsecret-global: docker pull secret for global container registry + const { otomi } = await this.getSettings(['otomi']) + const pullSecretConfig = (otomi as Record)?.globalPullSecret as + | { server?: string; username?: string; email?: string } + | undefined + if (pullSecretConfig) { + const otomiSecrets = await getSecretValues('otomi-secrets', APL_SECRETS_NAMESPACE) + const pullSecretPassword = otomiSecrets?.globalPullSecret_password + if (pullSecretPassword) { + const server = pullSecretConfig.server ?? 'docker.io' + const username = pullSecretConfig.username ?? '' + const email = pullSecretConfig.email ?? 'not@val.id' + const dockerConfig = JSON.stringify({ auths: { [server]: { username, password: pullSecretPassword, email } } }) + const yamlContent = await createPlatformSealedSecretManifest( + 'otomi-pullsecret-global', + teamNs, + { '.dockerconfigjson': dockerConfig }, + 'kubernetes.io/dockerconfigjson', + ) + await this.git.writeTextFile( + getNamespaceSealedSecretsValuesFilePath(teamNs, 'otomi-pullsecret-global'), + yamlContent, + ) + } + } + } + + /** + * Remove platform-managed SealedSecret files from a team namespace for disabled features. + * Called during editAplTeam to clean up secrets that are no longer needed. + * Uses removeFile which is a no-op if the file doesn't exist. + */ + private async cleanupTeamNamespaceSealedSecrets(teamId: string, spec: AplTeamSettingsRequest['spec']): Promise { + const teamNs = `team-${teamId}` + const grafanaEnabled = spec?.managedMonitoring?.grafana ?? false + const alertmanagerEnabled = spec?.managedMonitoring?.alertmanager ?? false + + if (!grafanaEnabled) { + for (const secretName of [`${teamNs}-grafana-admin`, 'grafana-oidc-secret', 'grafana-loki-datasource-secret']) { + const filePath = getNamespaceSealedSecretsValuesFilePath(teamNs, secretName) + this.fileStore.delete(filePath) + await this.git.removeFile(filePath) + } + } + + if (!alertmanagerEnabled) { + const filePath = getNamespaceSealedSecretsValuesFilePath(teamNs, 'alertmanager-credentials') + this.fileStore.delete(filePath) + await this.git.removeFile(filePath) + } + + const { otomi } = await this.getSettings(['otomi']) + const hasPullSecret = !!(otomi as Record)?.globalPullSecret + if (!hasPullSecret) { + const filePath = getNamespaceSealedSecretsValuesFilePath(teamNs, 'otomi-pullsecret-global') + this.fileStore.delete(filePath) + await this.git.removeFile(filePath) + } + } + async createTeam(data: Team): Promise { const newTeam = await this.createAplTeam(getAplObjectFromV1('AplTeamSettingSet', data) as AplTeamSettingsRequest) return getV1ObjectFromApl(newTeam) as Team @@ -951,9 +1111,11 @@ export default class OtomiStack { } // Encrypt password into a SealedSecret manifest + // Key is 'settings_password' to match apl-core's buildSecretToNamespaceMap convention: + // teamConfig..settings.password → group prefix 'teamConfig.' → relative path 'settings.password' → key 'settings_password' const sealedSecretName = `team-${teamName}-settings-secrets` const sealedSecretYaml = await createPlatformSealedSecretManifest(sealedSecretName, APL_SECRETS_NAMESPACE, { - password, + settings_password: password, }) const sealedSecretPath = getNamespaceSealedSecretsValuesFilePath(APL_SECRETS_NAMESPACE, sealedSecretName) await this.git.writeTextFile(sealedSecretPath, sealedSecretYaml) @@ -964,6 +1126,11 @@ export default class OtomiStack { const teamObject = toTeamObject(teamName, data) const team = await this.saveTeam(teamObject) + + // Create platform-managed SealedSecrets in the team namespace. + // Pass the plaintext password so it can be used before the apl-secrets SealedSecret is applied. + await this.createTeamNamespaceSealedSecrets(teamName, data.spec, password) + await this.doDeployment(team) return team.content as AplTeamSettingsResponse } @@ -985,12 +1152,30 @@ export default class OtomiStack { const teamObject = buildTeamObject(currentTeam, updatedSpec) const team = await this.saveTeam(teamObject) + + // Sync platform-managed SealedSecrets for the updated team spec: + // - Create/update secrets for enabled features + // - Remove secrets for disabled features + await this.createTeamNamespaceSealedSecrets(name, updatedSpec as AplTeamSettingsRequest['spec']) + await this.cleanupTeamNamespaceSealedSecrets(name, updatedSpec as AplTeamSettingsRequest['spec']) + await this.doDeployment(team) return team.content as AplTeamSettingsResponse } async deleteTeam(id: string): Promise { const filePaths = await this.deleteTeamObjects(id) + + // Also remove platform-managed SealedSecrets from env/manifests/namespaces/team-/ + const teamNsSealedSecretsPrefix = `env/manifests/namespaces/team-${id}/sealedsecrets/` + for (const key of this.fileStore.keys()) { + if (key.startsWith(teamNsSealedSecretsPrefix)) { + this.fileStore.delete(key) + filePaths.push(key) + } + } + await this.git.removeDir(`env/manifests/namespaces/team-${id}`) + await this.doDeleteDeployment(filePaths) } diff --git a/src/utils/sealedSecretUtils.ts b/src/utils/sealedSecretUtils.ts index 528fcc5de..dc8e60e2c 100644 --- a/src/utils/sealedSecretUtils.ts +++ b/src/utils/sealedSecretUtils.ts @@ -159,13 +159,19 @@ export function sealedSecretToUserData(manifest: SealedSecretManifestResponse): } /** - * Creates a SealedSecret manifest for a platform-level secret (not team-scoped). - * Used for secrets in apl-secrets, apl-users, and other platform namespaces. + * Creates a SealedSecret manifest for a platform-level or team-namespace secret. + * Encrypts each data field with the Sealed Secrets public key bound to the given namespace. + * + * @param name - Name of the SealedSecret (and resulting K8s Secret) + * @param namespace - Target namespace the secret will be decrypted in + * @param data - Key/value pairs to encrypt + * @param secretType - Optional Kubernetes secret type (defaults to 'kubernetes.io/opaque') */ export async function createPlatformSealedSecretManifest( name: string, namespace: string, data: Record, + secretType = 'kubernetes.io/opaque', ): Promise { const pem = await getSealedSecretsPEM() @@ -194,7 +200,7 @@ export async function createPlatformSealedSecretManifest( template: { immutable: false, metadata: { name, namespace }, - type: 'kubernetes.io/opaque', + type: secretType, }, }, } From 399c581693e59cb196cfd9fb56358d323e96212d Mon Sep 17 00:00:00 2001 From: otomi-admin <63190600+ferruhcihan@users.noreply.github.com> Date: Fri, 29 May 2026 00:11:36 +0200 Subject: [PATCH 2/5] feat: improve team secret management --- src/otomi-stack.ts | 170 --------------------------------------------- 1 file changed, 170 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 9d14998af..137c4446b 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -921,166 +921,6 @@ export default class OtomiStack { return settingsResponse } - /** - * Create or update platform-managed SealedSecrets in a team namespace. - * These replace ExternalSecrets that previously used core-secrets-store to read - * platform secrets — preventing teams from stealing other teams' or platform secrets. - * - * @param teamId - Team identifier (without 'team-' prefix) - * @param spec - Current team spec (used to check which features are enabled) - * @param teamPassword - Optional plaintext team password (only needed during team creation, - * before the SealedSecret is applied to K8s) - */ - private async createTeamNamespaceSealedSecrets( - teamId: string, - spec: AplTeamSettingsRequest['spec'], - teamPassword?: string, - ): Promise { - const teamNs = `team-${teamId}` - const grafanaEnabled = spec?.managedMonitoring?.grafana ?? false - const alertmanagerEnabled = spec?.managedMonitoring?.alertmanager ?? false - - if (grafanaEnabled) { - // team--grafana-admin: admin credentials for team's Grafana instance - let password = teamPassword - if (!password) { - const teamSecrets = await getSecretValues(`team-${teamId}-settings-secrets`, APL_SECRETS_NAMESPACE) - password = teamSecrets?.settings_password - } - if (password) { - const yamlContent = await createPlatformSealedSecretManifest(`${teamNs}-grafana-admin`, teamNs, { - 'admin-user': teamId, - 'admin-password': password, - }) - await this.git.writeTextFile( - getNamespaceSealedSecretsValuesFilePath(teamNs, `${teamNs}-grafana-admin`), - yamlContent, - ) - } - - // grafana-oidc-secret: Keycloak OIDC credentials for Grafana SSO - const keycloakSecrets = await getSecretValues('keycloak-secrets', APL_SECRETS_NAMESPACE) - const clientSecret = keycloakSecrets?.idp_clientSecret - const clientId = (this.getApp('keycloak').values as Record)?.idp?.clientID ?? '' - if (clientSecret) { - const yamlContent = await createPlatformSealedSecretManifest('grafana-oidc-secret', teamNs, { - client_id: clientId, - client_secret: clientSecret, - }) - await this.git.writeTextFile( - getNamespaceSealedSecretsValuesFilePath(teamNs, 'grafana-oidc-secret'), - yamlContent, - ) - } - - // grafana-loki-datasource-secret: Loki admin password for Grafana datasource - const lokiSecrets = await getSecretValues('loki-secrets', APL_SECRETS_NAMESPACE) - const lokiPassword = lokiSecrets?.adminPassword - if (lokiPassword) { - const yamlContent = await createPlatformSealedSecretManifest('grafana-loki-datasource-secret', teamNs, { - password: lokiPassword, - }) - await this.git.writeTextFile( - getNamespaceSealedSecretsValuesFilePath(teamNs, 'grafana-loki-datasource-secret'), - yamlContent, - ) - } - } - - if (alertmanagerEnabled) { - // alertmanager-credentials: notification channel credentials for team Alertmanager - const { alerts } = await this.getSettings(['alerts']) - const receivers = (spec?.alerts?.receivers ?? (alerts as Record)?.receivers ?? ['slack']) as string[] - const hasReceivers = !receivers.includes('none') - - if (hasReceivers) { - const alertData: Record = {} - - if (receivers.includes('slack')) { - const alertSecrets = await getSecretValues('alerts-secrets', APL_SECRETS_NAMESPACE) - if (alertSecrets?.slack_url) alertData.slackUrl = alertSecrets.slack_url - } - if (receivers.includes('email')) { - const smtpSecrets = await getSecretValues('smtp-secrets', APL_SECRETS_NAMESPACE) - if (smtpSecrets?.auth_password) alertData.smtpAuthPassword = smtpSecrets.auth_password - if (smtpSecrets?.auth_secret) alertData.smtpAuthSecret = smtpSecrets.auth_secret - } - if (receivers.includes('opsgenie')) { - // Legacy receiver — kept for backwards compatibility - const alertSecrets = await getSecretValues('alerts-secrets', APL_SECRETS_NAMESPACE) - if (alertSecrets?.opsgenie_apiKey) alertData.opsgenieApiKey = alertSecrets.opsgenie_apiKey - } - - if (Object.keys(alertData).length > 0) { - const yamlContent = await createPlatformSealedSecretManifest('alertmanager-credentials', teamNs, alertData) - await this.git.writeTextFile( - getNamespaceSealedSecretsValuesFilePath(teamNs, 'alertmanager-credentials'), - yamlContent, - ) - } - } - } - - // otomi-pullsecret-global: docker pull secret for global container registry - const { otomi } = await this.getSettings(['otomi']) - const pullSecretConfig = (otomi as Record)?.globalPullSecret as - | { server?: string; username?: string; email?: string } - | undefined - if (pullSecretConfig) { - const otomiSecrets = await getSecretValues('otomi-secrets', APL_SECRETS_NAMESPACE) - const pullSecretPassword = otomiSecrets?.globalPullSecret_password - if (pullSecretPassword) { - const server = pullSecretConfig.server ?? 'docker.io' - const username = pullSecretConfig.username ?? '' - const email = pullSecretConfig.email ?? 'not@val.id' - const dockerConfig = JSON.stringify({ auths: { [server]: { username, password: pullSecretPassword, email } } }) - const yamlContent = await createPlatformSealedSecretManifest( - 'otomi-pullsecret-global', - teamNs, - { '.dockerconfigjson': dockerConfig }, - 'kubernetes.io/dockerconfigjson', - ) - await this.git.writeTextFile( - getNamespaceSealedSecretsValuesFilePath(teamNs, 'otomi-pullsecret-global'), - yamlContent, - ) - } - } - } - - /** - * Remove platform-managed SealedSecret files from a team namespace for disabled features. - * Called during editAplTeam to clean up secrets that are no longer needed. - * Uses removeFile which is a no-op if the file doesn't exist. - */ - private async cleanupTeamNamespaceSealedSecrets(teamId: string, spec: AplTeamSettingsRequest['spec']): Promise { - const teamNs = `team-${teamId}` - const grafanaEnabled = spec?.managedMonitoring?.grafana ?? false - const alertmanagerEnabled = spec?.managedMonitoring?.alertmanager ?? false - - if (!grafanaEnabled) { - for (const secretName of [`${teamNs}-grafana-admin`, 'grafana-oidc-secret', 'grafana-loki-datasource-secret']) { - const filePath = getNamespaceSealedSecretsValuesFilePath(teamNs, secretName) - this.fileStore.delete(filePath) - await this.git.removeFile(filePath) - } - } - - if (!alertmanagerEnabled) { - const filePath = getNamespaceSealedSecretsValuesFilePath(teamNs, 'alertmanager-credentials') - this.fileStore.delete(filePath) - await this.git.removeFile(filePath) - } - - const { otomi } = await this.getSettings(['otomi']) - const hasPullSecret = !!(otomi as Record)?.globalPullSecret - if (!hasPullSecret) { - const filePath = getNamespaceSealedSecretsValuesFilePath(teamNs, 'otomi-pullsecret-global') - this.fileStore.delete(filePath) - await this.git.removeFile(filePath) - } - } - async createTeam(data: Team): Promise { const newTeam = await this.createAplTeam(getAplObjectFromV1('AplTeamSettingSet', data) as AplTeamSettingsRequest) return getV1ObjectFromApl(newTeam) as Team @@ -1121,10 +961,6 @@ export default class OtomiStack { const teamObject = toTeamObject(teamName, data) const team = await this.saveTeam(teamObject) - // Create platform-managed SealedSecrets in the team namespace. - // Pass the plaintext password so it can be used before the apl-secrets SealedSecret is applied. - await this.createTeamNamespaceSealedSecrets(teamName, data.spec, password) - await this.doDeployment(team) return team.content as AplTeamSettingsResponse } @@ -1147,12 +983,6 @@ export default class OtomiStack { const teamObject = buildTeamObject(currentTeam, updatedSpec) const team = await this.saveTeam(teamObject) - // Sync platform-managed SealedSecrets for the updated team spec: - // - Create/update secrets for enabled features - // - Remove secrets for disabled features - await this.createTeamNamespaceSealedSecrets(name, updatedSpec as AplTeamSettingsRequest['spec']) - await this.cleanupTeamNamespaceSealedSecrets(name, updatedSpec as AplTeamSettingsRequest['spec']) - await this.doDeployment(team) return team.content as AplTeamSettingsResponse } From d3e2f5cb45a7616a4c19b7e6440601bba5814886 Mon Sep 17 00:00:00 2001 From: otomi-admin <63190600+ferruhcihan@users.noreply.github.com> Date: Fri, 29 May 2026 00:57:06 +0200 Subject: [PATCH 3/5] chore: remove unnecessary changes --- src/otomi-stack.ts | 4 ---- src/utils/sealedSecretUtils.ts | 8 +------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 137c4446b..1021d6a67 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -945,8 +945,6 @@ export default class OtomiStack { } // Encrypt password into a SealedSecret manifest - // Key is 'settings_password' to match apl-core's buildSecretToNamespaceMap convention: - // teamConfig..settings.password → group prefix 'teamConfig.' → relative path 'settings.password' → key 'settings_password' const sealedSecretName = `team-${teamName}-settings-secrets` const sealedSecretYaml = await createPlatformSealedSecretManifest(sealedSecretName, APL_SECRETS_NAMESPACE, { settings_password: password, @@ -960,7 +958,6 @@ export default class OtomiStack { const teamObject = toTeamObject(teamName, data) const team = await this.saveTeam(teamObject) - await this.doDeployment(team) return team.content as AplTeamSettingsResponse } @@ -982,7 +979,6 @@ export default class OtomiStack { const teamObject = buildTeamObject(currentTeam, updatedSpec) const team = await this.saveTeam(teamObject) - await this.doDeployment(team) return team.content as AplTeamSettingsResponse } diff --git a/src/utils/sealedSecretUtils.ts b/src/utils/sealedSecretUtils.ts index dc8e60e2c..b99a4c840 100644 --- a/src/utils/sealedSecretUtils.ts +++ b/src/utils/sealedSecretUtils.ts @@ -161,17 +161,11 @@ export function sealedSecretToUserData(manifest: SealedSecretManifestResponse): /** * Creates a SealedSecret manifest for a platform-level or team-namespace secret. * Encrypts each data field with the Sealed Secrets public key bound to the given namespace. - * - * @param name - Name of the SealedSecret (and resulting K8s Secret) - * @param namespace - Target namespace the secret will be decrypted in - * @param data - Key/value pairs to encrypt - * @param secretType - Optional Kubernetes secret type (defaults to 'kubernetes.io/opaque') */ export async function createPlatformSealedSecretManifest( name: string, namespace: string, data: Record, - secretType = 'kubernetes.io/opaque', ): Promise { const pem = await getSealedSecretsPEM() @@ -200,7 +194,7 @@ export async function createPlatformSealedSecretManifest( template: { immutable: false, metadata: { name, namespace }, - type: secretType, + type: 'kubernetes.io/opaque', }, }, } From 80776d08814651373e945f042a6a097a6d1aee80 Mon Sep 17 00:00:00 2001 From: otomi-admin <63190600+ferruhcihan@users.noreply.github.com> Date: Fri, 29 May 2026 00:58:51 +0200 Subject: [PATCH 4/5] chore: remove unnecessary changes --- src/utils/sealedSecretUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/sealedSecretUtils.ts b/src/utils/sealedSecretUtils.ts index b99a4c840..528fcc5de 100644 --- a/src/utils/sealedSecretUtils.ts +++ b/src/utils/sealedSecretUtils.ts @@ -159,8 +159,8 @@ export function sealedSecretToUserData(manifest: SealedSecretManifestResponse): } /** - * Creates a SealedSecret manifest for a platform-level or team-namespace secret. - * Encrypts each data field with the Sealed Secrets public key bound to the given namespace. + * Creates a SealedSecret manifest for a platform-level secret (not team-scoped). + * Used for secrets in apl-secrets, apl-users, and other platform namespaces. */ export async function createPlatformSealedSecretManifest( name: string, From 18c3009a6acba9328245caa45274b8886f5b080e Mon Sep 17 00:00:00 2001 From: otomi-admin <63190600+ferruhcihan@users.noreply.github.com> Date: Fri, 29 May 2026 15:58:44 +0200 Subject: [PATCH 5/5] chore: remove unnecessary changes --- src/otomi-stack.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index a4119179b..cdad9c0ef 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -984,17 +984,6 @@ export default class OtomiStack { async deleteTeam(id: string): Promise { const filePaths = await this.deleteTeamObjects(id) - - // Also remove platform-managed SealedSecrets from env/manifests/namespaces/team-/ - const teamNsSealedSecretsPrefix = `env/manifests/namespaces/team-${id}/sealedsecrets/` - for (const key of this.fileStore.keys()) { - if (key.startsWith(teamNsSealedSecretsPrefix)) { - this.fileStore.delete(key) - filePaths.push(key) - } - } - await this.git.removeDir(`env/manifests/namespaces/team-${id}`) - await this.doDeleteDeployment(filePaths) }