Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@
"license": "MIT",
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
"dependencies": {
"@aws-sdk/client-ssm": "^3.990.0",
"@aws-sdk/client-ssm": "^3.1056.0",
"@aws-sdk/client-sso": "^3.1056.0",
"@aws-sdk/client-sso-oidc": "^3.1056.0",
"@inquirer/confirm": "^6.1.0",
"@inquirer/input": "^5.0.6",
"@inquirer/password": "^5.0.6",
"@inquirer/select": "^5.2.0",
"@napi-rs/keyring": "^1.2.0",
"commander": "^14.0.3",
"dotenv": "^17.3.1",
Expand Down
1,097 changes: 309 additions & 788 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions src/aws.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import type { Parameter } from '@aws-sdk/client-ssm'
import { DeleteParameterCommand, GetParameterCommand, PutParameterCommand, SSMClient, paginateGetParametersByPath } from '@aws-sdk/client-ssm'
import { getCredentials } from './keyring.js'
import { getAwsCredentials, getCredentialsRegion } from './credentials.js'
import { normalizePath, normalizePathAndName } from './utils.js'

export function getClient(): SSMClient {
const credentials = getCredentials()
const region = getCredentialsRegion()

return new SSMClient({
credentials: {
accessKeyId: credentials.accessKeyId,
secretAccessKey: credentials.secretAccessKey,
}, region: credentials.region,
credentials: getAwsCredentials,
region,
})
}

Expand Down
46 changes: 46 additions & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { spawn } from 'node:child_process'

export function openHttpsUrl(url: string): boolean {
if (!isSafeHttpsUrl(url)) {
return false
}

const command = getOpenCommand(url)
if (!command) {
return false
}

try {
const child = spawn(command.command, command.args, { detached: true, stdio: 'ignore' })
child.unref()
return true
}
catch {
return false
}
}

export function isSafeHttpsUrl(url: string): boolean {
try {
return new URL(url).protocol === 'https:'
}
catch {
return false
}
}

function getOpenCommand(url: string): { args: string[], command: string } | undefined {
if (process.platform === 'darwin') {
return { args: [url], command: 'open' }
}

if (process.platform === 'win32') {
return { args: ['/c', 'start', '""', url], command: 'cmd' }
}

if (process.platform === 'linux') {
return { args: [url], command: 'xdg-open' }
}

return undefined
}
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getCommand } from './commands/get.js'
import { putCommand } from './commands/put.js'
import { deleteCommand } from './commands/delete.js'
import { execCommand } from './commands/exec.js'
import { wipeCredentialsCommand } from './commands/wipe-credentials.js'

const program = new Command()
program.name('ssm-secrets').description('Simple AWS SSM secrets manager CLI').version(packageJson.version)
Expand All @@ -17,6 +18,7 @@ getCommand(program)
putCommand(program)
deleteCommand(program)
execCommand(program)
wipeCredentialsCommand(program)

try {
await program.parseAsync(process.argv)
Expand Down
36 changes: 30 additions & 6 deletions src/commands/auth.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
import { Command } from 'commander'
import promptInput from '@inquirer/input'
import promptPassword from '@inquirer/password'
import { writeCredentials } from '../keyring.js'
import { inputSsoCredentials, inputStaticCredentials } from '../credentials.js'

const SUMMARY = 'Authenticate and store AWS credentials securely'
const DESCRIPTION = `${SUMMARY}.
This will use the OS-specific keyring to store the Region, Access Key ID and Secret Access Key provided via an interactive prompt.
This will use the OS-specific keyring to store static AWS credentials or AWS SSO authentication state.
For more details, visit https://github.com/Brooooooklyn/keyring-node or its underlying library https://github.com/open-source-cooperative/keyring-rs`

const DEFAULT_REGION = 'eu-central-1'

interface AuthCommandOptions {
accountId?: string
region: string
roleName?: string
ssoStartUrl?: string
}

export function authCommand(program: Command) {
program
.command('auth')
.summary(SUMMARY)
.description(DESCRIPTION)
.action(async () => {
.option('--region <REGION>', 'AWS region')
.option('--sso-start-url <URL>', 'AWS SSO start URL')
.option('--account-id <ACCOUNT_ID>', 'AWS SSO account ID')
.option('--role-name <ROLE_NAME>', 'AWS SSO role name')
.action(async (options: AuthCommandOptions) => {
if (options.ssoStartUrl) {
await inputSsoCredentials({
accountId: options.accountId,
region: options.region || DEFAULT_REGION,
roleName: options.roleName,
startUrl: options.ssoStartUrl,
})
console.log('✅ SSO credentials securely stored in system keyring')
return
}

const answers = {
region: await promptInput({ message: 'AWS Region:', default: 'eu-central-1' }),
region: options.region ?? await promptInput({ message: 'AWS Region:', default: DEFAULT_REGION }),
accessKeyId: await promptInput({ message: 'AWS Access Key ID:', required: true }),
secretAccessKey: await promptPassword({
message: 'AWS Secret Access Key:',
Expand All @@ -24,7 +48,7 @@ export function authCommand(program: Command) {
}),
}

writeCredentials(answers)
console.log('✅ Credentials securely stored in system keyring')
inputStaticCredentials(answers)
console.log('✅ Static credentials securely stored in system keyring')
})
}
16 changes: 16 additions & 0 deletions src/commands/wipe-credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Command } from 'commander'
import { deleteCredentials } from '../keyring.js'

const SUMMARY = 'Delete stored AWS credentials'
const DESCRIPTION = `${SUMMARY}.`

export function wipeCredentialsCommand(program: Command) {
program
.command('wipe-credentials')
.summary(SUMMARY)
.description(DESCRIPTION)
.action(() => {
const deleted = deleteCredentials()
console.log(deleted ? '✅ Credentials deleted' : 'No credentials found')
})
}
Loading
Loading