Skip to content

Commit

Permalink
Merge pull request #2 from paul-unifra/main
Browse files Browse the repository at this point in the history
fix: bugs in AWS Secrets Manager push service
  • Loading branch information
dghelm authored Oct 17, 2024
2 parents 9c29d47 + a12f0bd commit 858f36a
Showing 1 changed file with 86 additions and 35 deletions.
121 changes: 86 additions & 35 deletions src/commands/setup/push-secrets.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { confirm, input, select } from '@inquirer/prompts'
import { Command, Flags } from '@oclif/core'
import chalk from 'chalk'
import { exec } from 'child_process'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as path from 'path'
import { exec } from 'child_process'
import { promisify } from 'util'
import { select, input, confirm } from '@inquirer/prompts'
import * as yaml from 'js-yaml'
import chalk from 'chalk'

const execAsync = promisify(exec)

Expand All @@ -14,32 +14,85 @@ interface SecretService {
}

class AWSSecretService implements SecretService {
constructor(private region: string, private prefixName: string) { }
constructor(private region: string, private prefixName: string, private debug: boolean) { }

private async convertToJson(filePath: string): Promise<string> {
const content = await fs.promises.readFile(filePath, 'utf-8')
const lines = content.split('\n')
const jsonContent: Record<string, string> = {}

for (const line of lines) {
if (line.trim() && !line.startsWith('#')) {
const [key, ...valueParts] = line.split(':')
const value = valueParts.join(':').trim()
jsonContent[key.trim()] = value.replace(/^"/, '').replace(/"$/, '')
private async secretExists(secretName: string): Promise<boolean> {
const fullSecretName = `${this.prefixName}/${secretName}`
try {
await execAsync(`aws secretsmanager describe-secret --secret-id "${fullSecretName}" --region ${this.region}`)
return true
} catch (error: any) {
if (error.message.includes('ResourceNotFoundException')) {
return false
}
throw error
}
}

private async createOrUpdateSecret(content: Record<string, string>, secretName: string): Promise<void> {
const fullSecretName = `${this.prefixName}/${secretName}`
const jsonContent = JSON.stringify(content)
const escapedJsonContent = jsonContent.replace(/'/g, "'\\''")

if (await this.secretExists(secretName)) {
const shouldOverride = await confirm({
message: chalk.yellow(`Secret ${fullSecretName} already exists. Do you want to override it?`),
default: false,
})

if (!shouldOverride) {
console.log(chalk.yellow(`Skipping secret: ${fullSecretName}`))
return
}

return JSON.stringify(jsonContent)
const command = `aws secretsmanager put-secret-value --secret-id "${fullSecretName}" --secret-string '${escapedJsonContent}' --region ${this.region}`
if (this.debug) {
console.log(chalk.yellow('--- Debug Output ---'))
console.log(chalk.cyan(`Command: ${command}`))
console.log(chalk.yellow('-------------------'))
}
try {
await execAsync(command)
console.log(chalk.green(`Successfully updated secret: ${fullSecretName}`))
} catch (error) {
console.error(chalk.red(`Failed to update secret: ${fullSecretName}`))
console.error(chalk.red(`Error details: ${error}`))
throw error
}
} else {
const command = `aws secretsmanager create-secret --name "${fullSecretName}" --secret-string '${escapedJsonContent}' --region ${this.region}`
if (this.debug) {
console.log(chalk.yellow('--- Debug Output ---'))
console.log(chalk.cyan(`Command: ${command}`))
console.log(chalk.yellow('-------------------'))
}
try {
await execAsync(command)
console.log(chalk.green(`Successfully created secret: ${fullSecretName}`))
} catch (error) {
console.error(chalk.red(`Failed to create secret: ${fullSecretName}`))
console.error(chalk.red(`Error details: ${error}`))
throw error
}
}
}

private async pushToAWSSecret(content: string, secretName: string): Promise<void> {
const command = `aws secretsmanager create-secret --name "${this.prefixName}/${secretName}" --secret-string "${JSON.stringify(content).slice(1, -1)}" --region ${this.region}`
try {
await execAsync(command)
console.log(chalk.green(`Successfully pushed secret: ${this.prefixName}/${secretName}`))
} catch (error) {
console.error(chalk.red(`Failed to push secret: ${this.prefixName}/${secretName}`))
private async convertEnvToDict(filePath: string): Promise<Record<string, string>> {
const content = await fs.promises.readFile(filePath, 'utf-8')
const result: Record<string, string> = {}

const lines = content.split('\n')
for (const line of lines) {
const match = line.match(/^([^=]+)=(.*)$/)
if (match) {
const key = match[1].trim()
let value = match[2].trim()
value = value.replace(/^["'](.*)["']$/, '$1')
result[key] = value
}
}

return result
}

async pushSecrets(): Promise<void> {
Expand All @@ -51,7 +104,7 @@ class AWSSecretService implements SecretService {
const secretName = path.basename(file, '.json')
console.log(chalk.cyan(`Processing JSON secret: ${secretName}`))
const content = await fs.promises.readFile(path.join(secretsDir, file), 'utf-8')
await this.pushToAWSSecret(content, secretName)
await this.createOrUpdateSecret({ 'migrate-db.json': content }, secretName)
}

// Process ENV files
Expand All @@ -62,20 +115,19 @@ class AWSSecretService implements SecretService {
const secretName = path.basename(file, '.env')
if (secretName.startsWith('l2-sequencer-')) {
console.log(chalk.cyan(`Processing L2 Sequencer secret: ${secretName}`))
const content = await this.convertToJson(path.join(secretsDir, file))
l2SequencerSecrets = { ...l2SequencerSecrets, ...JSON.parse(content) }
const data = await this.convertEnvToDict(path.join(secretsDir, file))
l2SequencerSecrets = { ...l2SequencerSecrets, ...data }
} else {
console.log(chalk.cyan(`Processing ENV secret: ${secretName}`))
const content = await this.convertToJson(path.join(secretsDir, file))
await this.pushToAWSSecret(content, secretName)
console.log(chalk.cyan(`Processing ENV secret: ${secretName}-env`))
const data = await this.convertEnvToDict(path.join(secretsDir, file))
await this.createOrUpdateSecret(data, `${secretName}-env`)
}
}

// Push combined L2 Sequencer secrets
if (Object.keys(l2SequencerSecrets).length > 0) {
console.log(chalk.cyan(`Processing combined L2 Sequencer secrets: l2-sequencer-secret`))
const combinedContent = JSON.stringify(l2SequencerSecrets)
await this.pushToAWSSecret(combinedContent, 'l2-sequencer-secret')
console.log(chalk.cyan(`Processing combined L2 Sequencer secrets: l2-sequencer-secret-env`))
await this.createOrUpdateSecret(l2SequencerSecrets, 'l2-sequencer-secret-env')
}
}
}
Expand Down Expand Up @@ -321,7 +373,6 @@ export default class SetupPushSecrets extends Command {
const valuesDir = path.join(process.cwd(), this.flags['values-dir']);
if (!fs.existsSync(valuesDir)) {
this.error(chalk.red(`Values directory not found at ${valuesDir}`));
return;
}

let credentials: Record<string, string>;
Expand Down Expand Up @@ -422,7 +473,7 @@ export default class SetupPushSecrets extends Command {

if (secretService === 'aws') {
const awsCredentials = await this.getAWSCredentials()
service = new AWSSecretService(awsCredentials.secretRegion, awsCredentials.prefixName)
service = new AWSSecretService(awsCredentials.secretRegion, awsCredentials.prefixName, flags.debug)
provider = 'aws'
} else if (secretService === 'vault') {
service = new HashicorpVaultDevService(flags.debug)
Expand Down Expand Up @@ -451,4 +502,4 @@ export default class SetupPushSecrets extends Command {
this.error(chalk.red(`Failed to push secrets: ${error}`))
}
}
}
}

0 comments on commit 858f36a

Please sign in to comment.