Skip to content

Commit 02b5495

Browse files
committed
Add ability to automatically remove an existing inactive key
1 parent 5d8f4fd commit 02b5495

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

exe/aws-rotate-keys

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
#!/usr/bin/env ruby
22

33
require "aws_rotate_keys"
4+
require "optparse"
45

5-
AwsRotateKeys::CLI.call
6+
# Use some basic parsing to allow command-line overrides of config
7+
class Parser
8+
def self.parse(options)
9+
output_options = {
10+
delete_inactive: false
11+
}
12+
13+
opt_parser = OptionParser.new do |opts|
14+
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
15+
16+
opts.on("--delete-inactive", "If the key quota is full but one is inactive, delete that key to make room") do |p|
17+
output_options[:delete_inactive] = p
18+
end
19+
20+
opts.on("-h", "--help", "Prints this help") do
21+
puts opts
22+
exit
23+
end
24+
end
25+
26+
opt_parser.parse!(options)
27+
output_options
28+
end
29+
end
30+
31+
cli_options = (Parser.parse ARGV)
32+
AwsRotateKeys::CLI.call(options: cli_options)

lib/aws_rotate_keys/cli.rb

+12-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ def self.call(*args)
1414
def initialize(iam: Aws::IAM::Client.new,
1515
credentials_path: "#{Dir.home}/.aws/credentials",
1616
stdout: $stdout,
17-
env: ENV)
17+
env: ENV,
18+
options: {})
1819
@iam = iam
1920
@credentials_path = credentials_path
2021
@stdout = stdout
2122
@env = env
23+
@options = options
2224
end
2325

2426
def call
@@ -31,12 +33,20 @@ def call
3133
if quota <= access_keys.size
3234
log "Key set is already at quota limit of #{quota}:"
3335
log_keylist(access_keys)
34-
raise "You must manually delete a key or use one of the command-line overrides"
36+
37+
inactive_keys = access_keys.select { |k| k["status"] == "Inactive" }
38+
if @options[:delete_inactive] && !inactive_keys.empty?
39+
log "Deleting oldest inactive access key as requested..."
40+
log_keylist(inactive_keys)
41+
delete_oldest_access_key(inactive_keys)
42+
else
43+
raise "You must manually delete a key or use one of the command-line overrides"
3544
end
3645
end
3746

3847
log "Creating access key..."
3948
new_key = create_access_key
49+
access_keys = aws_access_keys # refresh key list
4050

4151
if File.exist?(credentials_path)
4252
log "Backing up #{credentials_path} to #{credentials_backup_path}..."

spec/aws_rotate_keys_unsuccessfully_spec.rb

+38
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
describe AwsRotateKeys do
55
ACTIVE_KEY_ID = "ACTKEEEY".freeze
66
INACTIVE_KEY_ID = "INACTKEY".freeze
7+
ANOTHER_KEY_ID = "NEWKEEEY".freeze
8+
ANOTHER_SECRET = "SECRET123".freeze
79

810
class IAMAnotherDouble
911
def initialize
@@ -21,6 +23,21 @@ def initialize
2123
]
2224
end
2325

26+
def create_access_key
27+
@keys << Aws::IAM::Types::AccessKeyMetadata.new(
28+
access_key_id: ANOTHER_KEY_ID,
29+
status: "Active",
30+
create_date: Time.new(2017, 3, 1)
31+
)
32+
33+
Aws::IAM::Types::CreateAccessKeyResponse.new(
34+
access_key: Aws::IAM::Types::AccessKey.new(
35+
access_key_id: ANOTHER_KEY_ID,
36+
secret_access_key: ANOTHER_SECRET
37+
)
38+
)
39+
end
40+
2441
def list_access_keys
2542
Aws::IAM::Types::ListAccessKeysResponse.new(
2643
access_key_metadata: @keys
@@ -34,6 +51,10 @@ def get_account_summary
3451
}
3552
)
3653
end
54+
55+
def delete_access_key(access_key_id:)
56+
@keys.reject! { |k| k.access_key_id == access_key_id }
57+
end
3758
end
3859

3960
let(:iam_double) { IAMAnotherDouble.new }
@@ -57,4 +78,21 @@ def rotate_keys(args = {})
5778
end
5879
end
5980

81+
context "when at quota with override" do
82+
before do
83+
expect(iam_double).to receive(:delete_access_key).with(access_key_id: INACTIVE_KEY_ID).and_call_original
84+
expect(iam_double).to receive(:delete_access_key).with(access_key_id: ACTIVE_KEY_ID).and_call_original
85+
FileUtils.touch(credentials_path)
86+
end
87+
88+
it "deletes both the inactive key and the active key" do
89+
credentials_dir = File.dirname(credentials_path)
90+
credentials = Dir["#{credentials_dir}/*"]
91+
stdout = MyIO.new
92+
rotate_keys(stdout: stdout, options: { delete_inactive: true })
93+
expect(stdout.to_s).to include "Key set is already at quota limit"
94+
expect(stdout.to_s).to include "Deleting oldest inactive access key as requested"
95+
end
96+
end
97+
6098
end

0 commit comments

Comments
 (0)