-
Notifications
You must be signed in to change notification settings - Fork 1
feat: password v2 component #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
95ef3fe
Update redis component wrong imports
bornast cf9f385
Move password component to v2 folder
bornast ca3548c
Implement password component tests
bornast 410b3e4
Remove backOff config
bornast 23e3e5d
Add legacy prefix to password v1 component
bornast 0373827
Remove legacy prefix from v1 component
bornast 96b281a
Add password namespace
bornast e19018f
Update imports
bornast 201983d
Make password args optional
bornast e12f460
Fix test assertion
bornast e6cc125
Wrap password value using pulumi secret
bornast 73f3bf1
Implement test to check if password is a pulumi secret
bornast 2cf9afe
Remove console logs
bornast 7297f79
Export password using ES module syntax
bornast c1c2d05
Merge branch 'master' into task/password-v2-component
bornast 12ee89a
Move setup to the top-level to prevent false positives
bornast File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import * as aws from '@pulumi/aws'; | ||
| import * as pulumi from '@pulumi/pulumi'; | ||
| import * as random from '@pulumi/random'; | ||
| import { commonTags } from '../../../constants'; | ||
|
|
||
| export type PasswordArgs = { | ||
bornast marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| value?: pulumi.Input<string>; | ||
| }; | ||
|
|
||
| export class Password extends pulumi.ComponentResource { | ||
| name: string; | ||
| value: pulumi.Output<string>; | ||
| secret: aws.secretsmanager.Secret; | ||
|
|
||
| constructor( | ||
| name: string, | ||
| args: PasswordArgs, | ||
| opts: pulumi.ComponentResourceOptions = {}, | ||
| ) { | ||
| const optsWithDefauls = pulumi.mergeOptions(opts, { | ||
| additionalSecretOutputs: ['value'], | ||
| }); | ||
| super('studion:Password', name, {}, optsWithDefauls); | ||
|
|
||
| this.name = name; | ||
| if (args.value) { | ||
| this.value = pulumi.output(args.value); | ||
| } else { | ||
| const password = new random.RandomPassword( | ||
| `${this.name}-random-password`, | ||
| { | ||
| length: 16, | ||
| overrideSpecial: '_$', | ||
| special: true, | ||
| }, | ||
| { parent: this }, | ||
| ); | ||
| this.value = password.result; | ||
| } | ||
|
|
||
| this.secret = this.createPasswordSecret(this.value); | ||
| this.registerOutputs(); | ||
| } | ||
|
|
||
| private createPasswordSecret(password: pulumi.Input<string>) { | ||
| const project = pulumi.getProject(); | ||
| const stack = pulumi.getStack(); | ||
|
|
||
| const passwordSecret = new aws.secretsmanager.Secret( | ||
| `${this.name}-password-secret`, | ||
| { | ||
| namePrefix: `${stack}/${project}/${this.name}-`, | ||
| tags: commonTags, | ||
| }, | ||
| { parent: this }, | ||
| ); | ||
|
|
||
| const passwordSecretValue = new aws.secretsmanager.SecretVersion( | ||
| `${this.name}-password-secret-value`, | ||
| { | ||
| secretId: passwordSecret.id, | ||
| secretString: password, | ||
| }, | ||
| { parent: this, dependsOn: [passwordSecret] }, | ||
| ); | ||
|
|
||
| return passwordSecret; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import * as assert from 'node:assert'; | ||
| import { InlineProgramArgs } from '@pulumi/pulumi/automation'; | ||
| import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; | ||
| import { | ||
| DescribeSecretCommand, | ||
| GetSecretValueCommand, | ||
| } from '@aws-sdk/client-secrets-manager'; | ||
| import { PasswordTestContext } from './test-context'; | ||
| import { after, before, describe, it } from 'node:test'; | ||
| import * as automation from '../automation'; | ||
|
|
||
| const programArgs: InlineProgramArgs = { | ||
| stackName: 'dev', | ||
| projectName: 'icb-test-password', | ||
| program: () => import('./infrastructure'), | ||
| }; | ||
|
|
||
| describe('Password component deployment', () => { | ||
droguljic marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const region = process.env.AWS_REGION; | ||
| if (!region) { | ||
| throw new Error('AWS_REGION environment variable is required'); | ||
| } | ||
|
|
||
| const ctx: PasswordTestContext = { | ||
| outputs: {}, | ||
| config: { | ||
| autoGeneratedPasswordName: 'password-test-auto', | ||
| }, | ||
| clients: { | ||
| secretsManager: new SecretsManagerClient({ region }), | ||
| }, | ||
| }; | ||
|
|
||
| before(async () => { | ||
| ctx.outputs = await automation.deploy(programArgs); | ||
| }); | ||
|
|
||
| after(() => automation.destroy(programArgs)); | ||
|
|
||
| it('should create a password component with the correct configuration', async () => { | ||
| const password = ctx.outputs.autoGeneratedPassword.value; | ||
|
|
||
| assert.ok( | ||
| password.secret, | ||
| 'Password component should have secret property', | ||
| ); | ||
| assert.ok(password.value, 'Password component should have value property'); | ||
| assert.strictEqual( | ||
| password.name, | ||
| ctx.config.autoGeneratedPasswordName, | ||
| 'Password name should match input', | ||
| ); | ||
| }); | ||
|
|
||
| it('should create a secret with auto generated password', async () => { | ||
| const password = ctx.outputs.autoGeneratedPassword.value; | ||
|
|
||
| const secretResult = await ctx.clients.secretsManager.send( | ||
| new DescribeSecretCommand({ | ||
| SecretId: password.secret.arn, | ||
| }), | ||
| ); | ||
|
|
||
| assert.ok(secretResult.ARN, 'Secret should exist'); | ||
| assert.ok(secretResult.Name, 'Secret should have a name'); | ||
| assert.ok(secretResult.CreatedDate, 'Secret should have creation date'); | ||
|
|
||
| const expectedPrefix = `dev/${programArgs.projectName}/${ctx.config.autoGeneratedPasswordName}-`; | ||
bornast marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| assert.ok( | ||
| secretResult.Name?.startsWith(expectedPrefix), | ||
| `Secret name should start with ${expectedPrefix}`, | ||
| ); | ||
| }); | ||
|
|
||
| it('should generate a random password with correct format', async () => { | ||
| const password = ctx.outputs.autoGeneratedPassword.value; | ||
|
|
||
| const secretValue = await ctx.clients.secretsManager.send( | ||
| new GetSecretValueCommand({ | ||
| SecretId: password.secret.arn, | ||
| }), | ||
| ); | ||
|
|
||
| const passwordValue = secretValue.SecretString; | ||
| assert.ok(passwordValue, 'Password value should exist'); | ||
| assert.strictEqual( | ||
| passwordValue.length, | ||
| 16, | ||
| 'Password should be 16 characters long', | ||
| ); | ||
| assert.ok(secretValue.VersionId, 'Secret should have a version ID'); | ||
| }); | ||
|
|
||
| it('should create a secret with custom password value', async () => { | ||
| const password = ctx.outputs.customPassword.value; | ||
|
|
||
| const secretValue = await ctx.clients.secretsManager.send( | ||
| new GetSecretValueCommand({ | ||
| SecretId: password.secret.arn, | ||
| }), | ||
| ); | ||
|
|
||
| const passwordValue = secretValue.SecretString; | ||
| assert.strictEqual( | ||
| passwordValue, | ||
| 'customPass!', | ||
| 'Password should match custom value', | ||
| ); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { next as studion } from '@studion/infra-code-blocks'; | ||
|
|
||
| const appName = 'password-test'; | ||
|
|
||
| const autoGeneratedPassword = new studion.Password(`${appName}-auto`, {}); | ||
bornast marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const customPassword = new studion.Password(`${appName}-custom`, { | ||
| value: 'customPass!', | ||
| }); | ||
|
|
||
| module.exports = { | ||
bornast marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| autoGeneratedPassword, | ||
| customPassword, | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { OutputMap } from '@pulumi/pulumi/automation'; | ||
| import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; | ||
|
|
||
| interface PasswordTestConfig { | ||
| autoGeneratedPasswordName: string; | ||
| } | ||
|
|
||
| interface ConfigContext { | ||
| config: PasswordTestConfig; | ||
| } | ||
|
|
||
| interface PulumiProgramContext { | ||
| outputs: OutputMap; | ||
| } | ||
|
|
||
| interface AwsContext { | ||
| clients: { | ||
| secretsManager: SecretsManagerClient; | ||
| }; | ||
| } | ||
|
|
||
| export interface PasswordTestContext | ||
| extends ConfigContext, | ||
| PulumiProgramContext, | ||
| AwsContext {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.