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'