diff --git a/src/server/db/migrations/014_add_barbican_to_secret_providers.ts b/src/server/db/migrations/014_add_barbican_to_secret_providers.ts new file mode 100644 index 0000000..572dd0a --- /dev/null +++ b/src/server/db/migrations/014_add_barbican_to_secret_providers.ts @@ -0,0 +1,58 @@ +/** + * Copyright 2025 GoodRx, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + await knex.raw(` + UPDATE global_config + SET config = ( + (config::jsonb || '{ + "barbican": { + "enabled": true, + "clusterSecretStore": "barbican-secretsmanager", + "refreshInterval": "1h", + "allowedPrefixes": [] + } + }'::jsonb) + + || jsonb_set( + config::jsonb, + '{gcp,clusterSecretStore}', + '"gcp-secretsmanager"'::jsonb + ) + )::json, + "updatedAt" = now() + WHERE key = 'secretProviders'; + `); +} + +export async function down(knex: Knex): Promise { + await knex.raw(` + UPDATE global_config + SET config = ( + (config::jsonb - 'barbican') + + || jsonb_set( + config::jsonb, + '{gcp,clusterSecretStore}', + '"gcp-secretmanager"'::jsonb + ) + )::json, + "updatedAt" = now() + WHERE key = 'secretProviders'; + `); +} diff --git a/src/server/lib/envVariables.ts b/src/server/lib/envVariables.ts index 5c59320..f9f0eed 100644 --- a/src/server/lib/envVariables.ts +++ b/src/server/lib/envVariables.ts @@ -68,7 +68,7 @@ export abstract class EnvironmentVariables { buildUUID: string, fullYamlSupport: boolean, build: Build, - additionalVariables?: Record + additionalVariables?: Record, ): Promise> { let availableEnv: Record; @@ -185,7 +185,7 @@ export abstract class EnvironmentVariables { if (build == null) { throw Error( - 'Critical problem. Attempt retrieving environment Variables from empty build, which should NEVER happen.' + 'Critical problem. Attempt retrieving environment Variables from empty build, which should NEVER happen.', ); } @@ -196,7 +196,7 @@ export abstract class EnvironmentVariables { throw new LifecycleError( build.runUUID, '', - 'Critical problem. Missing associated deploys with the build, which should NEVER happen.' + 'Critical problem. Missing associated deploys with the build, which should NEVER happen.', ); } @@ -219,7 +219,7 @@ export abstract class EnvironmentVariables { */ async configurationServiceEnvironments( deploys: Deploy[], - fullYamlSupport: boolean + fullYamlSupport: boolean, ): Promise>> { const configurationDeploys = deploys.filter((deploy) => { const serviceType: DeployTypes = fullYamlSupport ? deploy.deployable?.type : deploy.service?.type; @@ -237,7 +237,7 @@ export abstract class EnvironmentVariables { .where('key', deploy.branchName) .first(); } - }) + }), ); return _.compact(configurations.map((configuration) => (configuration ? configuration.data : null))); @@ -262,7 +262,7 @@ export abstract class EnvironmentVariables { envString: Record, availableEnv: Record, useDefaultUUID: boolean, - namespace: string + namespace: string, ) { const str = JSON.stringify(envString || '').replace(/-/g, HYPHEN_REPLACEMENT); return await this.compileEnvironmentWithAvailableEnvironment(str, availableEnv, useDefaultUUID, namespace); @@ -278,7 +278,7 @@ export abstract class EnvironmentVariables { environment: string, availableEnv: Record, useDefaultUUID: boolean, - namespace: string + namespace: string, ) { return await this.customRender(environment, availableEnv, useDefaultUUID, namespace); } @@ -306,7 +306,7 @@ export abstract class EnvironmentVariables { * @returns the rendered template */ async customRender(template, data, useDefaultUUID = true, namespace: string) { - const secretPatternRegex = /\{\{(aws|gcp):([^}]+)\}\}/g; + const secretPatternRegex = /\{\{(aws|gcp|barbican|vault|onepassword):([^}]+)\}\}/g; const secretPlaceholders: Map = new Map(); let placeholderIndex = 0; @@ -364,7 +364,7 @@ export abstract class EnvironmentVariables { if (captureGroup.includes('_internalHostname')) { template = template.replace( fullMatch, - this.buildHostname({ host: data[captureGroup], suffix, rest, namespace: nsForDeploy }) + this.buildHostname({ host: data[captureGroup], suffix, rest, namespace: nsForDeploy }), ); } continue; @@ -378,14 +378,14 @@ export abstract class EnvironmentVariables { const defaultedInternalHostname = serviceToUpdate.replace(/_internalHostname$/, `-${defaultUuid}`); template = template.replace( fullMatch, - this.buildHostname({ host: defaultedInternalHostname, namespace: staticEnvNamespace, suffix, rest }) + this.buildHostname({ host: defaultedInternalHostname, namespace: staticEnvNamespace, suffix, rest }), ); } if (captureGroup.includes('_publicUrl')) { const serviceToUpdate = captureGroup.replace(HYPHEN_REPLACEMENT_REGEX, '-'); const defaultedPublicUrl = serviceToUpdate.replace( /_publicUrl$/, - `-${globalConfig.lifecycleDefaults.defaultPublicUrl}` + `-${globalConfig.lifecycleDefaults.defaultPublicUrl}`, ); getLogger().debug(`publicUrl for ${serviceToUpdate} defaulted to ${defaultedPublicUrl} using global_config`); template = template.replace(fullMatch, defaultedPublicUrl); @@ -405,6 +405,6 @@ export abstract class EnvironmentVariables { // eslint-disable-next-line no-unused-vars build: Build, // eslint-disable-next-line no-unused-vars - webhook?: any + webhook?: any, ): Promise>; } diff --git a/src/server/lib/kubernetes/__tests__/externalSecret.test.ts b/src/server/lib/kubernetes/__tests__/externalSecret.test.ts index 275b55c..a75851c 100644 --- a/src/server/lib/kubernetes/__tests__/externalSecret.test.ts +++ b/src/server/lib/kubernetes/__tests__/externalSecret.test.ts @@ -82,7 +82,7 @@ describe('externalSecret', () => { providerConfig, }); - expect(manifest.apiVersion).toBe('external-secrets.io/v1beta1'); + expect(manifest.apiVersion).toBe('external-secrets.io/v1'); expect(manifest.kind).toBe('ExternalSecret'); expect(manifest.metadata.name).toBe('api-server-aws-secrets'); expect(manifest.metadata.namespace).toBe('lfc-abc123'); diff --git a/src/server/lib/kubernetes/externalSecret.ts b/src/server/lib/kubernetes/externalSecret.ts index 04a4297..a631745 100644 --- a/src/server/lib/kubernetes/externalSecret.ts +++ b/src/server/lib/kubernetes/externalSecret.ts @@ -115,7 +115,7 @@ export function generateExternalSecretManifest(options: GenerateExternalSecretOp } return { - apiVersion: 'external-secrets.io/v1beta1', + apiVersion: 'external-secrets.io/v1', kind: 'ExternalSecret', metadata: { name: secretName, diff --git a/src/server/lib/secretRefs.ts b/src/server/lib/secretRefs.ts index dcd4c46..3f6e51a 100644 --- a/src/server/lib/secretRefs.ts +++ b/src/server/lib/secretRefs.ts @@ -31,7 +31,7 @@ export interface ValidationResult { error?: string; } -const SECRET_REF_REGEX = /^\{\{(aws|gcp):([^:}]+)(?::([^}]+))?\}\}$/; +const SECRET_REF_REGEX = /^\{\{(aws|gcp|barbican|vault|onepassword):([^:}]+)(?::([^}]+))?\}\}$/; export function isSecretRef(value: string): boolean { if (!value || typeof value !== 'string') { @@ -65,7 +65,7 @@ export function parseSecretRef(value: string): SecretRef | null { export function validateSecretRef( ref: SecretRef, - secretProviders: SecretProvidersConfig | undefined + secretProviders: SecretProvidersConfig | undefined, ): ValidationResult { if (!secretProviders) { return { valid: false, error: `Secret provider '${ref.provider}' not configured` };