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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/LICENSE.txt +201 -22
  4. data/README.md +134 -16
  5. data/lib/s3_secure.rb +3 -2
  6. data/lib/s3_secure/abstract_base.rb +2 -1
  7. data/lib/s3_secure/access_logs.rb +32 -0
  8. data/lib/s3_secure/access_logs/base.rb +4 -0
  9. data/lib/s3_secure/access_logs/disable.rb +37 -0
  10. data/lib/s3_secure/access_logs/enable.rb +41 -0
  11. data/lib/s3_secure/access_logs/list.rb +25 -0
  12. data/lib/s3_secure/access_logs/show.rb +89 -0
  13. data/lib/s3_secure/aws_services.rb +1 -30
  14. data/lib/s3_secure/aws_services/s3.rb +54 -0
  15. data/lib/s3_secure/cli.rb +27 -1
  16. data/lib/s3_secure/command.rb +7 -0
  17. data/lib/s3_secure/encryption.rb +4 -0
  18. data/lib/s3_secure/encryption/disable.rb +5 -9
  19. data/lib/s3_secure/encryption/enable.rb +5 -11
  20. data/lib/s3_secure/encryption/list.rb +12 -16
  21. data/lib/s3_secure/encryption/show.rb +14 -9
  22. data/lib/s3_secure/help/batch.md +14 -0
  23. data/lib/s3_secure/help/encryption/disable.md +5 -0
  24. data/lib/s3_secure/help/encryption/enable.md +6 -0
  25. data/lib/s3_secure/help/encryption/list.md +5 -0
  26. data/lib/s3_secure/help/lifecycle/add.md +13 -0
  27. data/lib/s3_secure/help/lifecycle/list.md +22 -0
  28. data/lib/s3_secure/help/lifecycle/remove.md +5 -0
  29. data/lib/s3_secure/help/lifecycle/show.md +13 -0
  30. data/lib/s3_secure/help/policy/enforce_ssl.md +34 -0
  31. data/lib/s3_secure/help/policy/list.md +5 -0
  32. data/lib/s3_secure/help/policy/unforce_ssl.md +61 -0
  33. data/lib/s3_secure/help/summary.md +22 -0
  34. data/lib/s3_secure/lifecycle.rb +33 -0
  35. data/lib/s3_secure/lifecycle/add.rb +33 -0
  36. data/lib/s3_secure/lifecycle/base.rb +5 -0
  37. data/lib/s3_secure/lifecycle/builder.rb +47 -0
  38. data/lib/s3_secure/lifecycle/list.rb +24 -0
  39. data/lib/s3_secure/lifecycle/remove.rb +28 -0
  40. data/lib/s3_secure/lifecycle/show.rb +40 -0
  41. data/lib/s3_secure/policy.rb +4 -0
  42. data/lib/s3_secure/policy/enforce.rb +6 -10
  43. data/lib/s3_secure/policy/list.rb +13 -17
  44. data/lib/s3_secure/policy/show.rb +11 -10
  45. data/lib/s3_secure/policy/unforce.rb +7 -10
  46. data/lib/s3_secure/remediate_all.rb +12 -0
  47. data/lib/s3_secure/say.rb +7 -0
  48. data/lib/s3_secure/summary.rb +13 -0
  49. data/lib/s3_secure/summary/item.rb +16 -0
  50. data/lib/s3_secure/summary/items.rb +65 -0
  51. data/lib/s3_secure/table.rb +18 -0
  52. data/lib/s3_secure/version.rb +1 -1
  53. data/lib/s3_secure/versioning.rb +31 -0
  54. data/lib/s3_secure/versioning/base.rb +4 -0
  55. data/lib/s3_secure/versioning/disable.rb +19 -0
  56. data/lib/s3_secure/versioning/enable.rb +19 -0
  57. data/lib/s3_secure/versioning/list.rb +24 -0
  58. data/lib/s3_secure/versioning/show.rb +27 -0
  59. data/s3-secure.gemspec +5 -2
  60. data/spec/lib/lifecycle/builder_spec.rb +85 -0
  61. metadata +72 -5
  62. 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
@@ -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
- @s3 = s3_regional_client(@bucket)
9
+ show = S3Secure::Policy::Show.new(@options)
10
10
 
11
- list = S3Secure::Policy::List.new(@options)
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
- puts "Bucket policy for #{@bucket} has ForceSSLOnlyAccess policy statement already:"
18
- puts bucket_policy
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
- @s3.put_bucket_policy(
24
+ s3.put_bucket_policy(
28
25
  bucket: @bucket,
29
26
  policy: policy_document,
30
27
  )
31
- puts "Add bucket policy to bucket #{@bucket}:"
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
- @s3 = s3_regional_client(bucket)
6
- puts "Policy for bucket #{bucket.color(:green)}"
7
- policy = get_policy(bucket)
8
+ $stderr.puts "Getting policy for bucket #{bucket.color(:green)}"
9
+ show = Show.new(bucket: bucket)
10
+ policy = show.policy
8
11
 
9
- if policy
10
- puts policy
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
- puts "Bucket does not have a bucket policy"
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
- # Useful when calling List outside of the list CLI
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
- puts "Bucket #{@bucket} is configured with this policy:"
12
- puts policy
13
- # puts policy.map(&:to_h)
5
+ say "Bucket #{@bucket} is configured with this policy:"
6
+ say policy
14
7
  else
15
- puts "Bucket #{@bucket} is not configured bucket policy"
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
- @s3 = s3_regional_client(@bucket)
9
+ show = S3Secure::Policy::Show.new(@options)
10
10
 
11
- list = S3Secure::Policy::List.new(@options)
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
- @s3.put_bucket_policy(
23
+ s3.put_bucket_policy(
27
24
  bucket: @bucket,
28
25
  policy: policy_document,
29
26
  )
30
27
  else
31
- @s3.delete_bucket_policy(bucket: @bucket)
28
+ s3.delete_bucket_policy(bucket: @bucket)
32
29
  end
33
30
 
34
- puts "Remove bucket policy to bucket #{@bucket}:"
35
- puts policy_document if policy_document
31
+ say "Remove bucket policy statement from bucket #{@bucket}:"
32
+ say policy_document if policy_document
36
33
  else
37
- puts "Bucket policy for #{@bucket} does not have ForceSSLOnlyAccess policy statement. Nothing to be done."
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,7 @@
1
+ module S3Secure
2
+ module Say
3
+ def say(msg)
4
+ puts msg unless @options[:quiet]
5
+ end
6
+ end
7
+ 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
@@ -1,3 +1,3 @@
1
1
  module S3Secure
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.1"
3
3
  end
@@ -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,4 @@
1
+ class S3Secure::Versioning
2
+ class Base < S3Secure::AbstractBase
3
+ end
4
+ 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