From 1775164614464ffbc762401fb0dbfa471ca5c186 Mon Sep 17 00:00:00 2001 From: Tristan Morgan Date: Fri, 31 Jan 2025 12:15:25 +1100 Subject: [PATCH] Generate fake creds for testing. --- i18n/en.yml | 1 + lib/awskeyring/awsapi.rb | 18 +++++++++++ lib/awskeyring_command.rb | 46 +++++++++++++++++++---------- man/awskeyring.5 | 10 +++++-- man/awskeyring.5.ronn | 6 ++-- spec/lib/awskeyring/awsapi_spec.rb | 6 ++++ spec/lib/awskeyring_command_spec.rb | 14 ++++++++- 7 files changed, 81 insertions(+), 20 deletions(-) diff --git a/i18n/en.yml b/i18n/en.yml index f5fd02f..3048234 100644 --- a/i18n/en.yml +++ b/i18n/en.yml @@ -33,6 +33,7 @@ en: notoken: 'Do not use saved token.' noremote: 'Do not validate with remote api.' path: 'The service PATH to open.' + test: 'Generate test credentials.' browser: 'Specify an alternative browser.' secret: 'AWS account secret.' unset: 'Unset environment variables.' diff --git a/lib/awskeyring/awsapi.rb b/lib/awskeyring/awsapi.rb index c380ddb..92cea6b 100644 --- a/lib/awskeyring/awsapi.rb +++ b/lib/awskeyring/awsapi.rb @@ -3,6 +3,7 @@ require 'aws-sdk-iam' require 'cgi' require 'json' +require 'securerandom' # Awskeyring Module, # gives you an interface to access keychains and items. @@ -180,6 +181,23 @@ def self.get_credentials_from_file(account:) } end + # Generate test credentials for AWS + # + # @return [Hash] with the new credentials + # key The aws_access_key_id + # secret The aws_secret_access_key + # expiry expiry time + def self.gen_test_credentials(account:) + { + account: account, + key: "AKIA#{Array.new(16) { [*'A'..'Z', *'2'..'7'].sample }.join}", + secret: SecureRandom.base64(30), + token: nil, + expiry: nil, + role: nil + } + end + # Retrieves an AWS Console login url # # @param [String] key The aws_access_key_id diff --git a/lib/awskeyring_command.rb b/lib/awskeyring_command.rb index bfa3fbc..fb704a9 100644 --- a/lib/awskeyring_command.rb +++ b/lib/awskeyring_command.rb @@ -109,10 +109,15 @@ def list_role desc 'env ACCOUNT', I18n.t('env_desc') method_option :force, type: :boolean, aliases: '-f', desc: I18n.t('method_option.force'), default: false method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false + method_option :test, type: :boolean, aliases: '-t', desc: I18n.t('method_option.test'), default: false method_option :unset, type: :boolean, aliases: '-u', desc: I18n.t('method_option.unset'), default: false # Print Env vars def env(account = nil) - if options[:unset] + if options[:test] + account ||= 'fakeaccount' + cred = Awskeyring::Awsapi.gen_test_credentials(account: account) + put_env_string(cred) + elsif options[:unset] put_env_string(account: nil, key: nil, secret: nil, token: nil) else output_safe(options[:force]) @@ -129,21 +134,32 @@ def env(account = nil) desc 'json ACCOUNT', I18n.t('json_desc') method_option :force, type: :boolean, aliases: '-f', desc: I18n.t('method_option.force'), default: false method_option 'no-token', type: :boolean, aliases: '-n', desc: I18n.t('method_option.notoken'), default: false + method_option :test, type: :boolean, aliases: '-t', desc: I18n.t('method_option.test'), default: false # Print JSON for use with credential_process - def json(account) # rubocop:disable Metrics/AbcSize - output_safe(options[:force]) - account = ask_check( - existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_exists), - limited_to: Awskeyring.list_account_names - ) - cred = age_check_and_get(account: account, no_token: options['no-token']) - expiry = Time.at(cred[:expiry]) unless cred[:expiry].nil? - puts Awskeyring::Awsapi.get_cred_json( - key: cred[:key], - secret: cred[:secret], - token: cred[:token], - expiry: (expiry || (Time.new + Awskeyring::Awsapi::ONE_HOUR)).iso8601 - ) + def json(account) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + if options[:test] + cred = Awskeyring::Awsapi.gen_test_credentials(account: account) + puts Awskeyring::Awsapi.get_cred_json( + key: cred[:key], + secret: cred[:secret], + token: cred[:token], + expiry: (Time.new + Awskeyring::Awsapi::TWELVE_HOUR).iso8601 + ) + else + output_safe(options[:force]) + account = ask_check( + existing: account, message: I18n.t('message.account'), validator: Awskeyring.method(:account_exists), + limited_to: Awskeyring.list_account_names + ) + cred = age_check_and_get(account: account, no_token: options['no-token']) + expiry = Time.at(cred[:expiry]) unless cred[:expiry].nil? + puts Awskeyring::Awsapi.get_cred_json( + key: cred[:key], + secret: cred[:secret], + token: cred[:token], + expiry: (expiry || (Time.new + Awskeyring::Awsapi::ONE_HOUR)).iso8601 + ) + end end desc 'import ACCOUNT', I18n.t('import_desc') diff --git a/man/awskeyring.5 b/man/awskeyring.5 index f01b7a5..fac66e8 100644 --- a/man/awskeyring.5 +++ b/man/awskeyring.5 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "AWSKEYRING" "5" "June 2024" "" "" +.TH "AWSKEYRING" "5" "January 2025" "" "" . .SH "NAME" \fBAwskeyring\fR \- is a small tool to manage AWS account keys in the macOS Keychain @@ -95,7 +95,10 @@ Outputs bourne shell environment exports for an ACCOUNT \-n, \-\-no\-token: Do not use saved token\. . .br -\-u, \-\-unset, \-\-no\-unset: Unset environment variables\. +\-t, \-\-test: Generate test credentials\. +. +.br +\-u, \-\-unset: Unset environment variables\. . .TP exec ACCOUNT command\.\.\.: @@ -153,6 +156,9 @@ Outputs AWS CLI compatible JSON for an ACCOUNT .br \-n, \-\-no\-token: Do not use saved token\. . +.br +\-t, \-\-test: Generate test credentials\. +. .TP list: . diff --git a/man/awskeyring.5.ronn b/man/awskeyring.5.ronn index ffe0208..e5ef705 100644 --- a/man/awskeyring.5.ronn +++ b/man/awskeyring.5.ronn @@ -50,7 +50,8 @@ The commands are as follows: -f, --force: Force output to a tty.
-n, --no-token: Do not use saved token.
- -u, --unset, --no-unset: Unset environment variables. + -t, --test: Generate test credentials.
+ -u, --unset: Unset environment variables. * exec ACCOUNT command...: @@ -80,7 +81,8 @@ The commands are as follows: Outputs AWS CLI compatible JSON for an ACCOUNT
-f, --force: Force output to a tty.
- -n, --no-token: Do not use saved token. + -n, --no-token: Do not use saved token.
+ -t, --test: Generate test credentials. * list: diff --git a/spec/lib/awskeyring/awsapi_spec.rb b/spec/lib/awskeyring/awsapi_spec.rb index eafb81a..96492ea 100644 --- a/spec/lib/awskeyring/awsapi_spec.rb +++ b/spec/lib/awskeyring/awsapi_spec.rb @@ -536,5 +536,11 @@ it 'calls get_account_id with an invalid token' do expect(awsapi.get_account_id(key: key)).to eq('000000000000') end + + it 'generates test credentials' do + creds = awsapi.gen_test_credentials(account: 'testaccount') + expect { Awskeyring::Validate.access_key(creds[:key]) }.not_to raise_error + expect { Awskeyring::Validate.secret_access_key(creds[:secret]) }.not_to raise_error + end end end diff --git a/spec/lib/awskeyring_command_spec.rb b/spec/lib/awskeyring_command_spec.rb index 0b1bdc5..07b9d9e 100644 --- a/spec/lib/awskeyring_command_spec.rb +++ b/spec/lib/awskeyring_command_spec.rb @@ -169,7 +169,7 @@ ENV['COMP_POINT'] = ENV['COMP_LINE'].size.to_s allow(ARGF).to receive(:argv).and_return(['awskeyring', '--', 'test']) expect { described_class.start(%w[autocomplete test]) } - .to output(/--force\n--no-token\n--unset/).to_stdout + .to output(/--force\n--no-token\n--test\n--unset/).to_stdout ENV['COMP_LINE'] = nil end @@ -245,6 +245,12 @@ )).to_stdout expect(Awskeyring).not_to have_received(:get_valid_creds) end + + it 'export a fake AWS Access key' do + expect { described_class.start(%w[env test --test]) } + .to output(/export AWS_DEFAULT_REGION="us-east-1"/).to_stdout + expect(Awskeyring).not_to have_received(:get_valid_creds) + end end context 'when there is an account, a role and a session token' do @@ -350,6 +356,12 @@ expect(Awskeyring).to have_received(:get_valid_creds).with(account: 'test', no_token: false) end + it 'provides a test account via JSON' do + expect { described_class.start(%w[json test --test]) } + .to output(/ "Version": 1,/).to_stdout + expect(Awskeyring).not_to have_received(:get_valid_creds).with(account: 'test', no_token: false) + end + it 'runs an external command' do ENV['BUNDLER_ORIG_TEST_ENV'] = 'BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL' ENV['TEST_ENV'] = 'CHANGED'