Skip to content

Commit d56e0af

Browse files
authored
Merge pull request #5 from CU-CloudCollab/add-vpc-checks
Extract IAM functionality from aws-config-check + add tests
2 parents 6fd7198 + 4b94b5c commit d56e0af

File tree

5 files changed

+939
-1
lines changed

5 files changed

+939
-1
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ Metrics/MethodLength:
99

1010
Metrics/AbcSize:
1111
Max: 50
12+
13+
# disable get/set warnings - many of our methods are performing API calls and get_ seems appropriate naming
14+
Style/AccessorMethodName:
15+
Enabled: false

lib/cucloud.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,31 @@ module Cucloud
66
require 'cucloud/ec2_utils'
77
require 'cucloud/asg_utils'
88
require 'cucloud/ssm_utils'
9+
require 'cucloud/iam_utils'
910

1011
DEFAULT_REGION = 'us-east-1'.freeze
1112

1213
Aws.config = { region: DEFAULT_REGION }
1314

15+
CORNELL_SAML_X509 = %(<ds:X509Certificate>MIIDSDCCAjCgAwIBAgIVAOZ8NfBem6sHcI7F39sYmD/JG4YDMA0GCSqGSIb3DQEB
16+
BQUAMCIxIDAeBgNVBAMTF3NoaWJpZHAuY2l0LmNvcm5lbGwuZWR1MB4XDTA5MTEy
17+
MzE4NTI0NFoXDTI5MTEyMzE4NTI0NFowIjEgMB4GA1UEAxMXc2hpYmlkcC5jaXQu
18+
Y29ybmVsbC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTURo9
19+
90uuODo/5ju3GZThcT67K3RXW69jwlBwfn3png75Dhyw9Xa50RFv0EbdfrojH1P1
20+
9LyfCjubfsm9Z7FYkVWSVdPSvQ0BXx7zQxdTpE9137qj740tMJr7Wi+iWdkyBQS/
21+
bCNhuLHeNQor6NXZoBgX8HvLy4sCUb/4v7vbp90HkmP3FzJRDevzgr6PVNqWwNqp
22+
tZ0vQHSF5D3iBNbxq3csfRGQQyVi729XuWMSqEjPhhkf1UjVcJ3/cG8tWbRKw+W+
23+
OIm71k+99kOgg7IvygndzzaGDVhDFMyiGZ4njMzEJT67sEq0pMuuwLMlLE/86mSv
24+
uGwO2Qacb1ckzjodAgMBAAGjdTBzMFIGA1UdEQRLMEmCF3NoaWJpZHAuY2l0LmNv
25+
cm5lbGwuZWR1hi5odHRwczovL3NoaWJpZHAuY2l0LmNvcm5lbGwuZWR1L2lkcC9z
26+
aGliYm9sZXRoMB0GA1UdDgQWBBSQgitoP2/rJMDepS1sFgM35xw19zANBgkqhkiG
27+
9w0BAQUFAAOCAQEAaFrLOGqMsbX1YlseO+SM3JKfgfjBBL5TP86qqiCuq9a1J6B7
28+
Yv+XYLmZBy04EfV0L7HjYX5aGIWLDtz9YAis4g3xTPWe1/bjdltUq5seRuksJjyb
29+
prGI2oAv/ShPBOyrkadectHzvu5K6CL7AxNTWCSXswtfdsuxcKo65tO5TRO1hWlr
30+
7Pq2F+Oj2hOvcwC0vOOjlYNe9yRE9DjJAzv4rrZUg71R3IEKNjfOF80LYPAFD2Sp
31+
p36uB6TmSYl1nBmS5LgWF4EpEuODPSmy4sIV6jl1otuyI/An2dOcNqcgu7tYEXLX
32+
C8N6DXggDWPtPRdpk96UW45huvXudpZenrcd7A==</ds:X509Certificate>).freeze
33+
1434
def region
1535
@region
1636
end

lib/cucloud/iam_utils.rb

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
module Cucloud
2+
# Utilities library for interacting with IAM
3+
class IamUtils
4+
# Define some error classes
5+
class UnknownComparisonOperatorError < StandardError
6+
end
7+
8+
def initialize(iam_client = Aws::IAM::Client.new)
9+
@iam = iam_client
10+
end
11+
12+
# Get the alias set for this account if it exists
13+
# @return [String] Account alias (nil if not set)
14+
def get_account_alias
15+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#list_account_aliases-instance_method
16+
# https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html
17+
# Per user guide: Account can have only one alias
18+
19+
@iam.list_account_aliases.account_aliases[0]
20+
end
21+
22+
# Get report about IAM entity usage and quotas in this account
23+
# @return [Hash<String,Integer>] A hash of key value pairs containing information about IAM entity usage and quotas.
24+
def get_account_summary
25+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#get_account_summary-instance_method
26+
# return https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Types/GetAccountSummaryResponse.html#summary_map-instance_method
27+
@iam.get_account_summary.summary_map
28+
end
29+
30+
# Does this account's root user have any API keys?
31+
# @return [Boolean]
32+
def root_user_has_api_key?
33+
get_account_summary['AccountAccessKeysPresent'] > 0
34+
end
35+
36+
# Does this account's root user have MFA enabled?
37+
# @return [Boolean]
38+
def root_user_mfa_enabled?
39+
get_account_summary['AccountMFAEnabled'] > 0
40+
end
41+
42+
# Does this account have multiple identity providers configured?
43+
# @return [Boolean]
44+
def multiple_providers_configured?
45+
get_account_summary['Providers'] > 1
46+
end
47+
48+
# Get password policy for this account
49+
# @return [Aws::IAM::Types::PasswordPolicy]
50+
def get_account_password_policy
51+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#get_account_password_policy-instance_method
52+
@iam.get_account_password_policy.password_policy
53+
end
54+
55+
# Check password policy against an options hash of audit criteria
56+
#
57+
# Policy format - Array of checks
58+
# example input: [{ key: "minimum_password_length", operator: "GT", value: 15 }]
59+
# example output: [{ key: "minimum_password_length", passes: true }]
60+
# @param [Array<Hash>] Policy against which to audit
61+
# @return [Array<Hash>] Results of each audit check
62+
# rubocop:disable Metrics/CyclomaticComplexity
63+
# disable complexity check here - doesn't seem worth breaking this function up
64+
def audit_password_policy(audit_criteria = [])
65+
policy_hash = get_account_password_policy.to_h
66+
67+
audit_array = []
68+
audit_criteria.each do |check|
69+
case check[:operator]
70+
when 'EQ'
71+
audit_array << {
72+
key: check[:key],
73+
passes: policy_hash[check[:key].to_sym].nil? ? false : policy_hash[check[:key].to_sym] == check[:value]
74+
}
75+
when 'LTE'
76+
audit_array << {
77+
key: check[:key],
78+
passes: policy_hash[check[:key].to_sym].nil? ? false : policy_hash[check[:key].to_sym] <= check[:value]
79+
}
80+
when 'GTE'
81+
audit_array << {
82+
key: check[:key],
83+
passes: policy_hash[check[:key].to_sym].nil? ? false : policy_hash[check[:key].to_sym] >= check[:value]
84+
}
85+
else
86+
raise UnknownComparisonOperatorError.new, "Unknown operator #{check[:operator]}"
87+
end
88+
end
89+
90+
audit_array
91+
end
92+
# rubocop:enable Metrics/CyclomaticComplexity
93+
94+
# Get SAML providers configured for this account
95+
# @return [Array<Hash>] Array of hashes in form { arn: <String>, metadata: <String> }
96+
def get_saml_providers
97+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#list_saml_providers-instance_method
98+
# returns https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Types/SAMLProviderListEntry.html
99+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#get_saml_provider-instance_method
100+
101+
provider_array = []
102+
@iam.list_saml_providers.saml_provider_list.each do |provider|
103+
provider_array << {
104+
arn: provider.arn,
105+
saml_metadata_document: @iam.get_saml_provider(saml_provider_arn: provider.arn).saml_metadata_document
106+
}
107+
end
108+
109+
provider_array
110+
end
111+
112+
# Is the Cornell SAML Identity Provider configured on this account?
113+
# @return [Boolean]
114+
def cornell_provider_configured?
115+
get_saml_providers.select { |provider| provider[:saml_metadata_document].include? CORNELL_SAML_X509 }.any?
116+
end
117+
118+
# Get users that are configured on this account
119+
# @return [Array<Hash>] Array of user hashes - base user type + added lookups for convenience
120+
def get_users
121+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#list_users-instance_method
122+
user_array = []
123+
@iam.list_users.users.each do |user|
124+
user_array << {
125+
base_data: user, # https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Types/User.html
126+
has_password: user_has_password?(user.user_name)
127+
}
128+
end
129+
user_array
130+
end
131+
132+
# Does this user have a password configured?
133+
# @param [String] Username
134+
# @return [Boolean]
135+
def user_has_password?(user_name)
136+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#get_login_profile-instance_method
137+
password = true
138+
139+
begin
140+
@iam.get_login_profile(user_name: user_name)
141+
rescue Aws::IAM::Errors::NoSuchEntity
142+
password = false
143+
end
144+
145+
password
146+
end
147+
148+
# Get access keys for user
149+
# @param [String] Username
150+
# @return [Array<Hash>] Array of key hashes - base key data + helper calculations for key age and active/inactive
151+
def get_user_access_keys(user_name)
152+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/IAM/Client.html#list_access_keys-instance_method
153+
keys = []
154+
@iam.list_access_keys(user_name: user_name).access_key_metadata.each do |key|
155+
keys << {
156+
base_data: key,
157+
active: key.status == 'Active',
158+
days_old: (Time.now - key.create_date).to_i / (24 * 60 * 60)
159+
}
160+
end
161+
162+
keys
163+
end
164+
165+
# Get active access keys on account that are older than specified age (in days)
166+
# @param [Integer] Days old
167+
# @return [Array<Hash>]
168+
def get_active_keys_older_than_n_days(n)
169+
keys = []
170+
get_users.each do |user|
171+
keys << get_user_access_keys(user[:base_data].user_name).select { |k| k[:days_old] > n && k[:active] }
172+
end
173+
174+
keys.flatten
175+
end
176+
end
177+
end

lib/cucloud/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module Cucloud
22
# Disable mutable constant warning - freezing this oddly breaks bundler
33
# rubocop:disable Style/MutableConstant
4-
VERSION = '0.2.0'
4+
VERSION = '0.3.0'
55
end

0 commit comments

Comments
 (0)