s3-secure 0.3.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +201 -22
- data/README.md +134 -16
- data/lib/s3_secure.rb +3 -2
- data/lib/s3_secure/abstract_base.rb +2 -1
- data/lib/s3_secure/access_logs.rb +32 -0
- data/lib/s3_secure/access_logs/base.rb +4 -0
- data/lib/s3_secure/access_logs/disable.rb +37 -0
- data/lib/s3_secure/access_logs/enable.rb +41 -0
- data/lib/s3_secure/access_logs/list.rb +25 -0
- data/lib/s3_secure/access_logs/show.rb +89 -0
- data/lib/s3_secure/aws_services.rb +1 -30
- data/lib/s3_secure/aws_services/s3.rb +54 -0
- data/lib/s3_secure/cli.rb +27 -1
- data/lib/s3_secure/command.rb +7 -0
- data/lib/s3_secure/encryption.rb +4 -0
- data/lib/s3_secure/encryption/disable.rb +5 -9
- data/lib/s3_secure/encryption/enable.rb +5 -11
- data/lib/s3_secure/encryption/list.rb +12 -16
- data/lib/s3_secure/encryption/show.rb +14 -9
- data/lib/s3_secure/help/batch.md +14 -0
- data/lib/s3_secure/help/encryption/disable.md +5 -0
- data/lib/s3_secure/help/encryption/enable.md +6 -0
- data/lib/s3_secure/help/encryption/list.md +5 -0
- data/lib/s3_secure/help/lifecycle/add.md +13 -0
- data/lib/s3_secure/help/lifecycle/list.md +22 -0
- data/lib/s3_secure/help/lifecycle/remove.md +5 -0
- data/lib/s3_secure/help/lifecycle/show.md +13 -0
- data/lib/s3_secure/help/policy/enforce_ssl.md +34 -0
- data/lib/s3_secure/help/policy/list.md +5 -0
- data/lib/s3_secure/help/policy/unforce_ssl.md +61 -0
- data/lib/s3_secure/help/summary.md +22 -0
- data/lib/s3_secure/lifecycle.rb +33 -0
- data/lib/s3_secure/lifecycle/add.rb +33 -0
- data/lib/s3_secure/lifecycle/base.rb +5 -0
- data/lib/s3_secure/lifecycle/builder.rb +47 -0
- data/lib/s3_secure/lifecycle/list.rb +24 -0
- data/lib/s3_secure/lifecycle/remove.rb +28 -0
- data/lib/s3_secure/lifecycle/show.rb +40 -0
- data/lib/s3_secure/policy.rb +4 -0
- data/lib/s3_secure/policy/enforce.rb +6 -10
- data/lib/s3_secure/policy/list.rb +13 -17
- data/lib/s3_secure/policy/show.rb +11 -10
- data/lib/s3_secure/policy/unforce.rb +7 -10
- data/lib/s3_secure/remediate_all.rb +12 -0
- data/lib/s3_secure/say.rb +7 -0
- data/lib/s3_secure/summary.rb +13 -0
- data/lib/s3_secure/summary/item.rb +16 -0
- data/lib/s3_secure/summary/items.rb +65 -0
- data/lib/s3_secure/table.rb +18 -0
- data/lib/s3_secure/version.rb +1 -1
- data/lib/s3_secure/versioning.rb +31 -0
- data/lib/s3_secure/versioning/base.rb +4 -0
- data/lib/s3_secure/versioning/disable.rb +19 -0
- data/lib/s3_secure/versioning/enable.rb +19 -0
- data/lib/s3_secure/versioning/list.rb +24 -0
- data/lib/s3_secure/versioning/show.rb +27 -0
- data/s3-secure.gemspec +5 -2
- data/spec/lib/lifecycle/builder_spec.rb +85 -0
- metadata +72 -5
- data/lib/s3_secure/help/hello.md +0 -5
@@ -0,0 +1,32 @@
|
|
1
|
+
module S3Secure
|
2
|
+
class AccessLogs < Command
|
3
|
+
class_option :quiet, type: :boolean
|
4
|
+
|
5
|
+
desc "list", "List bucket access_logs setting"
|
6
|
+
long_desc Help.text("access_logs/list")
|
7
|
+
option :format, desc: "Format options: #{CliFormat.formats.join(', ')}"
|
8
|
+
option :access_logs, type: :boolean, desc: "Filter for access_logs: all, true, false"
|
9
|
+
def list
|
10
|
+
List.new(options).run
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "show BUCKET", "show bucket access_logs"
|
14
|
+
long_desc Help.text("access_logs/show")
|
15
|
+
def show(bucket)
|
16
|
+
Show.new(options.merge(bucket: bucket)).run
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "enable BUCKET", "enable bucket access_logs"
|
20
|
+
long_desc Help.text("access_logs/enable")
|
21
|
+
option :target_bucket, desc: "Target s3 bucket"
|
22
|
+
def enable(bucket)
|
23
|
+
Enable.new(options.merge(bucket: bucket)).run
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "disable BUCKET", "disable bucket access_logs"
|
27
|
+
long_desc Help.text("access_logs/disable")
|
28
|
+
def disable(bucket)
|
29
|
+
Disable.new(options.merge(bucket: bucket)).run
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class S3Secure::AccessLogs
|
2
|
+
class Disable < Base
|
3
|
+
def run
|
4
|
+
@show = Show.new(bucket: @bucket)
|
5
|
+
|
6
|
+
remove_access_logging
|
7
|
+
remove_bucket_acl
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove_access_logging
|
11
|
+
unless @show.logging_enabled?
|
12
|
+
say "Bucket #{@bucket} is not configured with access logging. So nothing to remove."
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
s3.put_bucket_logging(
|
17
|
+
bucket: @bucket, # source
|
18
|
+
bucket_logging_status: {}, # empty hash to remove
|
19
|
+
)
|
20
|
+
say "Bucket #{@bucket} access logging removed"
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_bucket_acl
|
24
|
+
unless @show.acl_enabled?
|
25
|
+
say "Bucket #{@bucket} is not configured the log delivery ACL. So nothing to remove."
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
access_control_policy = @show.access_control_policy_without_log_delivery_permissions
|
30
|
+
s3.put_bucket_acl(
|
31
|
+
bucket: @bucket,
|
32
|
+
access_control_policy: access_control_policy,
|
33
|
+
)
|
34
|
+
say "Bucket #{@bucket} ACL Log Delivery removed"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class S3Secure::AccessLogs
|
2
|
+
class Enable < Base
|
3
|
+
def run
|
4
|
+
@show = Show.new(bucket: @bucket)
|
5
|
+
add_bucket_acl
|
6
|
+
enable_access_logging
|
7
|
+
end
|
8
|
+
|
9
|
+
# Bucket ACL applies on the target bucket only
|
10
|
+
def add_bucket_acl
|
11
|
+
if @show.acl_enabled?
|
12
|
+
say "Bucket acl already has log delivery ACL"
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
s3.put_bucket_acl(
|
17
|
+
bucket: @bucket,
|
18
|
+
access_control_policy: @show.access_control_policy_with_log_delivery_permissions,
|
19
|
+
)
|
20
|
+
say "Added to bucket acl that grants log delivery"
|
21
|
+
end
|
22
|
+
|
23
|
+
def enable_access_logging
|
24
|
+
if @show.logging_enabled?
|
25
|
+
say "Bucket access logging already enabled"
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
s3.put_bucket_logging(
|
30
|
+
bucket: @bucket, # source
|
31
|
+
bucket_logging_status: {
|
32
|
+
logging_enabled: {
|
33
|
+
target_bucket: @show.target_bucket,
|
34
|
+
target_prefix: @show.target_prefix,
|
35
|
+
},
|
36
|
+
},
|
37
|
+
)
|
38
|
+
say "Enabled access logging on the source bucket #{@bucket} to be delivered to the target bucket #{@show.target_bucket}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class S3Secure::AccessLogs
|
2
|
+
class List < Base
|
3
|
+
def run
|
4
|
+
presenter = CliFormat::Presenter.new(@options)
|
5
|
+
presenter.header = ["Bucket", "Access Logs?"]
|
6
|
+
|
7
|
+
buckets.each do |bucket|
|
8
|
+
$stderr.puts "Getting access log setting for bucket #{bucket.color(:green)}"
|
9
|
+
show = Show.new(bucket: bucket)
|
10
|
+
|
11
|
+
enabled = show.logging_enabled?
|
12
|
+
row = [bucket, enabled]
|
13
|
+
if @options[:enabled].nil?
|
14
|
+
presenter.rows << row # always show policy
|
15
|
+
elsif @options[:enabled]
|
16
|
+
presenter.rows << row if enabled # only show if bucket has some encryption rules
|
17
|
+
else
|
18
|
+
presenter.rows << row unless enabled # only show if bucket doesnt have any encryption rules
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
presenter.show
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class S3Secure::AccessLogs
|
2
|
+
class Show < Base
|
3
|
+
def run
|
4
|
+
say "Bucket ACL:"
|
5
|
+
pp bucket_acl_grants
|
6
|
+
say "Bucket Logging:"
|
7
|
+
pp bucket_logging
|
8
|
+
end
|
9
|
+
|
10
|
+
def bucket_logging
|
11
|
+
# Tricky here, need to swtich the s3 client in case target_bucket is in another region
|
12
|
+
with_regional_s3(target_bucket) do
|
13
|
+
s3.get_bucket_logging(bucket: target_bucket).to_h
|
14
|
+
end
|
15
|
+
end
|
16
|
+
memoize :bucket_logging
|
17
|
+
|
18
|
+
def bucket_acl
|
19
|
+
# Tricky here, need to swtich the s3 client in case target_bucket is in another region
|
20
|
+
with_regional_s3(target_bucket) do
|
21
|
+
s3.get_bucket_acl(bucket: target_bucket)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
memoize :bucket_acl
|
25
|
+
|
26
|
+
def bucket_acl_grants
|
27
|
+
bucket_acl.grants.map(&:to_h)
|
28
|
+
end
|
29
|
+
|
30
|
+
def enabled?
|
31
|
+
acl_enabled? && logging_enabled?
|
32
|
+
end
|
33
|
+
|
34
|
+
def acl_enabled?
|
35
|
+
grants = bucket_acl_grants & log_delivery_access_grants
|
36
|
+
!grants.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def logging_enabled?
|
40
|
+
!bucket_logging.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
def log_delivery_access_grants
|
44
|
+
[
|
45
|
+
{
|
46
|
+
grantee: {type: "Group", uri: "http://acs.amazonaws.com/groups/s3/LogDelivery"},
|
47
|
+
permission: "WRITE"
|
48
|
+
},{
|
49
|
+
grantee: {type: "Group", uri: "http://acs.amazonaws.com/groups/s3/LogDelivery"},
|
50
|
+
permission: "READ_ACP"
|
51
|
+
}
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
def access_control_policy_with_log_delivery_permissions
|
56
|
+
grants = bucket_acl_grants + log_delivery_access_grants
|
57
|
+
{ grants: grants, owner: owner }
|
58
|
+
end
|
59
|
+
|
60
|
+
def access_control_policy_without_log_delivery_permissions
|
61
|
+
grants = bucket_acl_grants - log_delivery_access_grants
|
62
|
+
{ grants: grants, owner: owner }
|
63
|
+
end
|
64
|
+
|
65
|
+
def owner
|
66
|
+
{
|
67
|
+
display_name: bucket_acl.owner.display_name,
|
68
|
+
id: bucket_acl.owner.id,
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def target_bucket
|
73
|
+
@options[:target_bucket] || @bucket
|
74
|
+
end
|
75
|
+
|
76
|
+
def target_prefix
|
77
|
+
prefix = @options[:target_prefix] || "access-logs"
|
78
|
+
prefix += "/" unless prefix.ends_with?("/")
|
79
|
+
prefix
|
80
|
+
end
|
81
|
+
|
82
|
+
def with_regional_s3(bucket)
|
83
|
+
current_bucket, @bucket = @bucket, bucket
|
84
|
+
result = yield
|
85
|
+
@bucket = current_bucket
|
86
|
+
result
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -2,35 +2,6 @@ require "aws-sdk-s3"
|
|
2
2
|
|
3
3
|
module S3Secure
|
4
4
|
module AwsServices
|
5
|
-
|
6
|
-
|
7
|
-
@@buckets = {} # holds bucket => region map
|
8
|
-
def s3_regional_client(bucket)
|
9
|
-
region = @@buckets[bucket]
|
10
|
-
|
11
|
-
unless region
|
12
|
-
resp = s3_client.get_bucket_location(bucket: bucket)
|
13
|
-
region = resp.location_constraint
|
14
|
-
region = 'us-east-1' if region.empty? # "" means us-east-1
|
15
|
-
end
|
16
|
-
|
17
|
-
new_s3_regional_client(region)
|
18
|
-
end
|
19
|
-
|
20
|
-
def new_s3_regional_client(region=nil)
|
21
|
-
options = {}
|
22
|
-
options[:endpoint] = "https://s3.#{region}.amazonaws.com" if region
|
23
|
-
options[:region] = region if region
|
24
|
-
Aws::S3::Client.new(options)
|
25
|
-
end
|
26
|
-
memoize :new_s3_regional_client
|
27
|
-
|
28
|
-
# Generic s3 client. Will be configured to whatever region user has locally configured in ~/.aws/config
|
29
|
-
# Used to call get_bucket_location to get each specific bucket's location.
|
30
|
-
# Generally use the s3_regional_client instead of this.
|
31
|
-
def s3_client
|
32
|
-
Aws::S3::Client.new
|
33
|
-
end
|
34
|
-
memoize :s3_client
|
5
|
+
include S3
|
35
6
|
end
|
36
7
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module S3Secure::AwsServices
|
2
|
+
module S3
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
@@s3_clients = {} # holds cached s3 regional clients cache
|
6
|
+
def s3
|
7
|
+
check_bucket!
|
8
|
+
@@s3_clients[@bucket] ||= new_s3_regional_client
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_s3_regional_client
|
12
|
+
options = {}
|
13
|
+
options[:endpoint] = "https://s3.#{region}.amazonaws.com"
|
14
|
+
options[:region] = region
|
15
|
+
Aws::S3::Client.new(options)
|
16
|
+
rescue Aws::STS::Errors::RegionDisabledException
|
17
|
+
puts "ERROR: Fail to establish client connection to region #{region}".color(:red)
|
18
|
+
raise
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generic s3 client. Will be configured to whatever region user has locally configured in ~/.aws/config
|
22
|
+
# Used to call get_bucket_location to get each specific bucket's location.
|
23
|
+
# Generally use the s3_regional_client instead of this.
|
24
|
+
def s3_client
|
25
|
+
Aws::S3::Client.new
|
26
|
+
end
|
27
|
+
memoize :s3_client
|
28
|
+
|
29
|
+
def check_bucket!
|
30
|
+
# IMPORANT: The class that includes this module must set @bucket before using the s3 method.
|
31
|
+
unless @bucket
|
32
|
+
raise "@bucket #{@bucket.inspect} is not set. The class must set @bucket before using the any client method."
|
33
|
+
end
|
34
|
+
region_map # triggers building region map for specific @bucket
|
35
|
+
end
|
36
|
+
|
37
|
+
@@region_map = {} # bucket to region map cache
|
38
|
+
def region_map
|
39
|
+
region = @@region_map[@bucket]
|
40
|
+
return @@region_map if region # return cache
|
41
|
+
|
42
|
+
# build cache
|
43
|
+
resp = s3_client.get_bucket_location(bucket: @bucket)
|
44
|
+
region = resp.location_constraint
|
45
|
+
region = 'us-east-1' if region.empty? # "" means us-east-1
|
46
|
+
@@region_map[@bucket] = region
|
47
|
+
@@region_map
|
48
|
+
end
|
49
|
+
|
50
|
+
def region
|
51
|
+
region_map[@bucket]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/s3_secure/cli.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
module S3Secure
|
2
2
|
class CLI < Command
|
3
|
-
class_option :
|
3
|
+
class_option :quiet, type: :boolean
|
4
4
|
class_option :noop, type: :boolean
|
5
5
|
|
6
|
+
desc "access_logs SUBCOMMAND", "access_logs subcommands"
|
7
|
+
long_desc Help.text(:access_logs)
|
8
|
+
subcommand "access_logs", AccessLogs
|
9
|
+
|
6
10
|
desc "encryption SUBCOMMAND", "encryption subcommands"
|
7
11
|
long_desc Help.text(:encryption)
|
8
12
|
subcommand "encryption", Encryption
|
@@ -11,6 +15,28 @@ module S3Secure
|
|
11
15
|
long_desc Help.text(:policy)
|
12
16
|
subcommand "policy", Policy
|
13
17
|
|
18
|
+
desc "versioning SUBCOMMAND", "versioning subcommands"
|
19
|
+
long_desc Help.text(:versioning)
|
20
|
+
subcommand "versioning", Versioning
|
21
|
+
|
22
|
+
desc "lifecycle SUBCOMMAND", "lifecycle subcommands"
|
23
|
+
long_desc Help.text(:lifecycle)
|
24
|
+
subcommand "lifecycle", Lifecycle
|
25
|
+
|
26
|
+
desc "remediate_all BUCKET", "Remediate all. For more fine-grain control use each of the commands directly."
|
27
|
+
long_desc Help.text("remediate_all")
|
28
|
+
def remediate_all(bucket)
|
29
|
+
RemediateAll.new(options.merge(bucket: bucket)).run
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "summary", "Summarize buckets"
|
33
|
+
long_desc Help.text("summary")
|
34
|
+
option :encrypted, default: "any", desc: "filter for encryption enabled. Examples: any, yes, no"
|
35
|
+
option :ssl, default: "any", desc: "filter for ssl enforcement. Examples: any, yes, no"
|
36
|
+
def summary
|
37
|
+
Summary.new(options).run
|
38
|
+
end
|
39
|
+
|
14
40
|
desc "batch *PARAMS", "Batch wrapper method"
|
15
41
|
long_desc Help.text(:batch)
|
16
42
|
def batch(*params)
|
data/lib/s3_secure/command.rb
CHANGED
@@ -77,6 +77,13 @@ module S3Secure
|
|
77
77
|
def website
|
78
78
|
""
|
79
79
|
end
|
80
|
+
|
81
|
+
# https://github.com/erikhuda/thor/issues/244
|
82
|
+
# Deprecation warning: Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `Lono::CLI`
|
83
|
+
# You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.
|
84
|
+
def exit_on_failure?
|
85
|
+
true
|
86
|
+
end
|
80
87
|
end
|
81
88
|
end
|
82
89
|
end
|
data/lib/s3_secure/encryption.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
module S3Secure
|
2
2
|
class Encryption < Command
|
3
|
+
class_option :quiet, type: :boolean
|
4
|
+
|
3
5
|
desc "list", "List bucket encryptions"
|
4
6
|
long_desc Help.text("encryption/list")
|
7
|
+
option :format, desc: "Format options: #{CliFormat.formats.join(', ')}"
|
8
|
+
option :encryption, type: :boolean, desc: "Filter for encryption: all, true, false"
|
5
9
|
def list
|
6
10
|
List.new(options).run
|
7
11
|
end
|
@@ -1,17 +1,13 @@
|
|
1
1
|
class S3Secure::Encryption
|
2
2
|
class Disable < Base
|
3
3
|
def run
|
4
|
-
|
4
|
+
show = Show.new(@options)
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
rules = list.get_encryption_rules(@bucket)
|
10
|
-
if rules
|
11
|
-
@s3.delete_bucket_encryption(bucket: @bucket) # returns resp = #<struct Aws::EmptyStructure>
|
12
|
-
puts "Bucket #{@bucket} encryption has been removed"
|
6
|
+
if show.enabled?
|
7
|
+
s3.delete_bucket_encryption(bucket: @bucket) # returns resp = #<struct Aws::EmptyStructure>
|
8
|
+
say "Bucket #{@bucket} encryption has been removed"
|
13
9
|
else
|
14
|
-
|
10
|
+
say "Bucket #{@bucket} is not configured with encryption at the bucket level"
|
15
11
|
end
|
16
12
|
end
|
17
13
|
end
|
@@ -1,16 +1,11 @@
|
|
1
1
|
class S3Secure::Encryption
|
2
2
|
class Enable < Base
|
3
3
|
def run
|
4
|
-
|
4
|
+
show = Show.new(@options)
|
5
5
|
|
6
|
-
|
7
|
-
list.set_s3(@s3)
|
8
|
-
|
9
|
-
rules = list.get_encryption_rules(@bucket)
|
10
|
-
if rules
|
6
|
+
if show.enabled?
|
11
7
|
# check rules to see if encryption is already set of some sort
|
12
|
-
|
13
|
-
puts rules.map(&:to_h)
|
8
|
+
say "Bucket #{@bucket} already has encryption rules:"
|
14
9
|
else
|
15
10
|
# Set encryption rules
|
16
11
|
# Ruby docs: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_encryption-instance_method
|
@@ -18,12 +13,11 @@ class S3Secure::Encryption
|
|
18
13
|
#
|
19
14
|
# put_bucket_encryption returns #<struct Aws::EmptyStructure>
|
20
15
|
#
|
21
|
-
|
16
|
+
s3.put_bucket_encryption(
|
22
17
|
bucket: @bucket,
|
23
18
|
server_side_encryption_configuration: {
|
24
19
|
rules: [rule]})
|
25
|
-
|
26
|
-
pp rule
|
20
|
+
say "Encyption enabled on bucket #{@bucket} with rules:"
|
27
21
|
end
|
28
22
|
end
|
29
23
|
|