s3-secure 0.3.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,28 @@
|
|
1
|
+
class S3Secure::Lifecycle
|
2
|
+
class Remove < Base
|
3
|
+
RULE_ID = Base::RULE_ID
|
4
|
+
|
5
|
+
def run
|
6
|
+
show = Show.new(@options)
|
7
|
+
unless show.has?(RULE_ID)
|
8
|
+
say "Bucket #{@bucket} already does not have the #{RULE_ID} lifecycle rule."
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
builder = Builder.new(show.get_lifecycle_rules(@bucket))
|
13
|
+
rules = builder.rules_with_removal
|
14
|
+
if rules.empty?
|
15
|
+
s3.delete_bucket_lifecycle(bucket: @bucket)
|
16
|
+
else
|
17
|
+
# update config with removal
|
18
|
+
s3.put_bucket_lifecycle_configuration(
|
19
|
+
bucket: @bucket, # required
|
20
|
+
# content_md5: "ContentMD5",
|
21
|
+
lifecycle_configuration: {rules: rules}
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
say "Removed the #{RULE_ID} lifecycle rule on bucket #{@bucket}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class S3Secure::Lifecycle
|
2
|
+
class Show < Base
|
3
|
+
RULE_ID = Base::RULE_ID
|
4
|
+
|
5
|
+
def run
|
6
|
+
if any?
|
7
|
+
say "This S3 bucket has lifecycle rules"
|
8
|
+
else
|
9
|
+
say "This S3 bucket does not have lifecycle rules"
|
10
|
+
end
|
11
|
+
|
12
|
+
if any?
|
13
|
+
say "Bucket lifecycle details: "
|
14
|
+
pp get_lifecycle(@bucket).to_h
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def any?
|
19
|
+
rules = get_lifecycle_rules(@bucket)
|
20
|
+
!!(rules && !rules.empty?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def has?(rule_id)
|
24
|
+
rules = get_lifecycle_rules(@bucket)
|
25
|
+
rules && rules.detect { |rule| rule[:id] == rule_id }
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_lifecycle(bucket)
|
29
|
+
s3.get_bucket_lifecycle_configuration(bucket: bucket) # resp
|
30
|
+
rescue Aws::S3::Errors::NoSuchLifecycleConfiguration
|
31
|
+
end
|
32
|
+
memoize :get_lifecycle
|
33
|
+
|
34
|
+
# Also used by add and remove
|
35
|
+
def get_lifecycle_rules(bucket)
|
36
|
+
resp = get_lifecycle(bucket)
|
37
|
+
resp.rules.map(&:to_h) if resp
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/s3_secure/policy.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
module S3Secure
|
2
2
|
class Policy < Command
|
3
|
+
class_option :quiet, type: :boolean
|
4
|
+
|
3
5
|
desc "list", "List bucket policies"
|
4
6
|
long_desc Help.text("policy/list")
|
7
|
+
option :format, desc: "Format options: #{CliFormat.formats.join(', ')}"
|
8
|
+
option :policy, type: :boolean, desc: "Filter for policy: all, true, false"
|
5
9
|
def list
|
6
10
|
List.new(options).run
|
7
11
|
end
|
@@ -6,16 +6,13 @@ class S3Secure::Policy
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def run
|
9
|
-
|
9
|
+
show = S3Secure::Policy::Show.new(@options)
|
10
10
|
|
11
|
-
|
12
|
-
list.set_s3(@s3)
|
13
|
-
|
14
|
-
bucket_policy = list.get_policy(@bucket)
|
11
|
+
bucket_policy = show.policy
|
15
12
|
document = Document.new(@bucket, bucket_policy)
|
16
13
|
if document.has?(@sid)
|
17
|
-
|
18
|
-
|
14
|
+
say "Bucket policy for #{@bucket} has ForceSSLOnlyAccess policy statement already:"
|
15
|
+
say bucket_policy
|
19
16
|
else
|
20
17
|
# Set encryption rules
|
21
18
|
# Ruby docs: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_policy-instance_method
|
@@ -24,12 +21,11 @@ class S3Secure::Policy
|
|
24
21
|
# put_bucket_policy returns #<struct Aws::EmptyStructure>
|
25
22
|
#
|
26
23
|
policy_document = document.policy_document(@sid)
|
27
|
-
|
24
|
+
s3.put_bucket_policy(
|
28
25
|
bucket: @bucket,
|
29
26
|
policy: policy_document,
|
30
27
|
)
|
31
|
-
|
32
|
-
puts policy_document
|
28
|
+
say "Add bucket policy to bucket #{@bucket}:"
|
33
29
|
end
|
34
30
|
end
|
35
31
|
end
|
@@ -1,29 +1,25 @@
|
|
1
1
|
class S3Secure::Policy
|
2
2
|
class List < Base
|
3
3
|
def run
|
4
|
+
presenter = CliFormat::Presenter.new(@options)
|
5
|
+
presenter.header = ["Bucket", "Has Policy?"]
|
6
|
+
|
4
7
|
buckets.each do |bucket|
|
5
|
-
|
6
|
-
|
7
|
-
policy =
|
8
|
+
$stderr.puts "Getting policy for bucket #{bucket.color(:green)}"
|
9
|
+
show = Show.new(bucket: bucket)
|
10
|
+
policy = show.policy
|
8
11
|
|
9
|
-
|
10
|
-
|
12
|
+
row = [bucket, !!policy]
|
13
|
+
if @options[:policy].nil?
|
14
|
+
presenter.rows << row # always show policy
|
15
|
+
elsif @options[:policy]
|
16
|
+
presenter.rows << row if policy # only show if bucket has a policy
|
11
17
|
else
|
12
|
-
|
18
|
+
presenter.rows << row unless policy # only show if bucket doesnt have a policy
|
13
19
|
end
|
14
20
|
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def get_policy(bucket)
|
18
|
-
resp = @s3.get_bucket_policy(bucket: bucket)
|
19
|
-
data = JSON.load(resp.policy.read) # String
|
20
|
-
JSON.pretty_generate(data)
|
21
|
-
rescue Aws::S3::Errors::NoSuchBucketPolicy
|
22
|
-
end
|
23
21
|
|
24
|
-
|
25
|
-
def set_s3(client)
|
26
|
-
@s3 = client
|
22
|
+
presenter.show
|
27
23
|
end
|
28
24
|
end
|
29
25
|
end
|
@@ -1,19 +1,20 @@
|
|
1
1
|
class S3Secure::Policy
|
2
2
|
class Show < Base
|
3
3
|
def run
|
4
|
-
@s3 = s3_regional_client(@bucket)
|
5
|
-
|
6
|
-
list = S3Secure::Policy::List.new(@options)
|
7
|
-
list.set_s3(@s3)
|
8
|
-
|
9
|
-
policy = list.get_policy(@bucket)
|
10
4
|
if policy
|
11
|
-
|
12
|
-
|
13
|
-
# puts policy.map(&:to_h)
|
5
|
+
say "Bucket #{@bucket} is configured with this policy:"
|
6
|
+
say policy
|
14
7
|
else
|
15
|
-
|
8
|
+
say "Bucket #{@bucket} is not configured bucket policy"
|
16
9
|
end
|
17
10
|
end
|
11
|
+
|
12
|
+
def policy
|
13
|
+
resp = s3.get_bucket_policy(bucket: @bucket)
|
14
|
+
data = JSON.load(resp.policy.read) # String
|
15
|
+
JSON.pretty_generate(data)
|
16
|
+
rescue Aws::S3::Errors::NoSuchBucketPolicy
|
17
|
+
end
|
18
|
+
memoize :policy
|
18
19
|
end
|
19
20
|
end
|
@@ -6,12 +6,9 @@ class S3Secure::Policy
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def run
|
9
|
-
|
9
|
+
show = S3Secure::Policy::Show.new(@options)
|
10
10
|
|
11
|
-
|
12
|
-
list.set_s3(@s3)
|
13
|
-
|
14
|
-
bucket_policy = list.get_policy(@bucket)
|
11
|
+
bucket_policy = show.policy
|
15
12
|
document = Document.new(@bucket, bucket_policy, remove: true)
|
16
13
|
if document.has?(@sid)
|
17
14
|
# Set encryption rules
|
@@ -23,18 +20,18 @@ class S3Secure::Policy
|
|
23
20
|
policy_document = document.policy_document(@sid)
|
24
21
|
|
25
22
|
if policy_document
|
26
|
-
|
23
|
+
s3.put_bucket_policy(
|
27
24
|
bucket: @bucket,
|
28
25
|
policy: policy_document,
|
29
26
|
)
|
30
27
|
else
|
31
|
-
|
28
|
+
s3.delete_bucket_policy(bucket: @bucket)
|
32
29
|
end
|
33
30
|
|
34
|
-
|
35
|
-
|
31
|
+
say "Remove bucket policy statement from bucket #{@bucket}:"
|
32
|
+
say policy_document if policy_document
|
36
33
|
else
|
37
|
-
|
34
|
+
say "Bucket policy for #{@bucket} does not have ForceSSLOnlyAccess policy statement. Nothing to be done."
|
38
35
|
end
|
39
36
|
end
|
40
37
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module S3Secure
|
2
|
+
class RemediateAll < AbstractBase
|
3
|
+
def run
|
4
|
+
o = @options.merge(bucket: @bucket)
|
5
|
+
Encryption::Enable.new(o).run
|
6
|
+
Policy::Enforce.new(o.merge(sid: "ForceSSLOnlyAccess")).run
|
7
|
+
Versioning::Enable.new(o).run
|
8
|
+
Lifecycle::Add.new(o).run
|
9
|
+
AccessLogs::Enable.new(o).run
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module S3Secure
|
2
|
+
class Summary < AbstractBase
|
3
|
+
def run
|
4
|
+
$stderr.puts("Determining bucket security-related settings. Can take a while for lots of buckets...")
|
5
|
+
data = [%w[Bucket SSL? Encrypted?]]
|
6
|
+
items = Items.new(@options, buckets)
|
7
|
+
items.filtered_items.each do |i|
|
8
|
+
data << [i.bucket, i.ssl, i.encrypted]
|
9
|
+
end
|
10
|
+
S3Secure::Table.new(@options, data).display
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class S3Secure::Summary
|
2
|
+
class Item
|
3
|
+
attr_reader :bucket
|
4
|
+
def initialize(bucket, properties={})
|
5
|
+
@bucket, @properties = bucket, properties
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(name, *args, &block)
|
9
|
+
if @properties.key?(name)
|
10
|
+
@properties[name]
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class S3Secure::Summary
|
2
|
+
class Items < S3Secure::AbstractBase
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
# override initialize
|
6
|
+
def initialize(options, buckets)
|
7
|
+
@options, @buckets = options, buckets
|
8
|
+
@ssl, @encrypted = @options[:ssl], @options[:encrypted]
|
9
|
+
end
|
10
|
+
|
11
|
+
def filtered_items
|
12
|
+
items = all_items.select do |item|
|
13
|
+
case @ssl
|
14
|
+
when "yes", "no"
|
15
|
+
@ssl == item.ssl
|
16
|
+
else # any or fallback
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
items.select do |item|
|
22
|
+
case @encrypted
|
23
|
+
when "yes", "no"
|
24
|
+
@encrypted == item.encrypted
|
25
|
+
else # any or fallback
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Triggers loading of items
|
32
|
+
def all_items
|
33
|
+
load_items!
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_items!
|
37
|
+
@buckets.map do |bucket|
|
38
|
+
Item.new(bucket,
|
39
|
+
ssl: ssl?(bucket) ? "yes" : "no",
|
40
|
+
encrypted: encrypted?(bucket) ? "yes" : "no")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
memoize :load_items!
|
44
|
+
|
45
|
+
private
|
46
|
+
def ssl?(bucket)
|
47
|
+
list = S3Secure::Policy::List.new(@options)
|
48
|
+
|
49
|
+
bucket_policy = list.get_policy(bucket)
|
50
|
+
document = S3Secure::Policy::Document.new(bucket, bucket_policy)
|
51
|
+
document.has?("ForceSSLOnlyAccess")
|
52
|
+
end
|
53
|
+
memoize :ssl?
|
54
|
+
|
55
|
+
def encrypted?(bucket)
|
56
|
+
s3 = s3_regional_client(bucket)
|
57
|
+
list = S3Secure::Encryption::List.new(@options)
|
58
|
+
list.set_s3(s3)
|
59
|
+
|
60
|
+
rules = list.get_encryption_rules(bucket)
|
61
|
+
!!rules
|
62
|
+
end
|
63
|
+
memoize :encrypted?
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "text-table"
|
2
|
+
|
3
|
+
module S3Secure
|
4
|
+
class Table
|
5
|
+
attr_reader :data
|
6
|
+
def initialize(options, data)
|
7
|
+
@options = options
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def display
|
12
|
+
table = Text::Table.new
|
13
|
+
table.head = data.shift
|
14
|
+
table.rows = data
|
15
|
+
puts table
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/s3_secure/version.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module S3Secure
|
2
|
+
class Versioning < Command
|
3
|
+
class_option :quiet, type: :boolean
|
4
|
+
|
5
|
+
desc "list", "List bucket versionings"
|
6
|
+
long_desc Help.text("versioning/list")
|
7
|
+
option :format, desc: "Format options: #{CliFormat.formats.join(', ')}"
|
8
|
+
option :versioning, desc: "Filter for versioning: all, true, false"
|
9
|
+
def list
|
10
|
+
List.new(options).run
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "show BUCKET", "show bucket versioning"
|
14
|
+
long_desc Help.text("versioning/show")
|
15
|
+
def show(bucket)
|
16
|
+
Show.new(options.merge(bucket: bucket)).run
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "enable BUCKET", "enable bucket versioning"
|
20
|
+
long_desc Help.text("versioning/enable")
|
21
|
+
def enable(bucket)
|
22
|
+
Enable.new(options.merge(bucket: bucket)).run
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "disable BUCKET", "disable bucket versioning"
|
26
|
+
long_desc Help.text("versioning/disable")
|
27
|
+
def disable(bucket)
|
28
|
+
Disable.new(options.merge(bucket: bucket)).run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class S3Secure::Versioning
|
2
|
+
class Disable < Base
|
3
|
+
def run
|
4
|
+
show = Show.new(@options)
|
5
|
+
if show.enabled?
|
6
|
+
s3.put_bucket_versioning(
|
7
|
+
bucket: @bucket,
|
8
|
+
versioning_configuration: {
|
9
|
+
# mfa_delete: "Disabled",
|
10
|
+
status: "Suspended",
|
11
|
+
},
|
12
|
+
)
|
13
|
+
say "Versioning Suspended on bucket #{@bucket}"
|
14
|
+
else
|
15
|
+
say "Bucket #{@bucket} is already has versioning already Suspended or not Enabled."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class S3Secure::Versioning
|
2
|
+
class Enable < Base
|
3
|
+
def run
|
4
|
+
show = Show.new(@options)
|
5
|
+
if show.enabled?
|
6
|
+
say "Bucket #{@bucket} is has versioning already enabled."
|
7
|
+
else
|
8
|
+
s3.put_bucket_versioning(
|
9
|
+
bucket: @bucket,
|
10
|
+
versioning_configuration: {
|
11
|
+
# mfa_delete: "Disabled",
|
12
|
+
status: "Enabled",
|
13
|
+
},
|
14
|
+
)
|
15
|
+
say "Versioning enabled on bucket #{@bucket}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|