s3-secure 0.4.2 → 0.5.0

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE.txt +201 -22
  4. data/README.md +41 -17
  5. data/lib/s3_secure.rb +3 -2
  6. data/lib/s3_secure/access_logs.rb +30 -0
  7. data/lib/s3_secure/access_logs/base.rb +4 -0
  8. data/lib/s3_secure/access_logs/disable.rb +37 -0
  9. data/lib/s3_secure/access_logs/enable.rb +41 -0
  10. data/lib/s3_secure/access_logs/list.rb +25 -0
  11. data/lib/s3_secure/access_logs/show.rb +89 -0
  12. data/lib/s3_secure/aws_services.rb +1 -33
  13. data/lib/s3_secure/aws_services/s3.rb +54 -0
  14. data/lib/s3_secure/cli.rb +19 -1
  15. data/lib/s3_secure/command.rb +7 -0
  16. data/lib/s3_secure/encryption.rb +2 -0
  17. data/lib/s3_secure/encryption/disable.rb +4 -8
  18. data/lib/s3_secure/encryption/enable.rb +4 -8
  19. data/lib/s3_secure/encryption/list.rb +12 -16
  20. data/lib/s3_secure/encryption/show.rb +11 -6
  21. data/lib/s3_secure/help/batch.md +14 -0
  22. data/lib/s3_secure/help/encryption/list.md +5 -0
  23. data/lib/s3_secure/help/lifecycle/add.md +13 -0
  24. data/lib/s3_secure/help/lifecycle/list.md +22 -0
  25. data/lib/s3_secure/help/lifecycle/remove.md +5 -0
  26. data/lib/s3_secure/help/lifecycle/show.md +13 -0
  27. data/lib/s3_secure/help/policy/list.md +5 -0
  28. data/lib/s3_secure/lifecycle.rb +31 -0
  29. data/lib/s3_secure/lifecycle/add.rb +33 -0
  30. data/lib/s3_secure/lifecycle/base.rb +5 -0
  31. data/lib/s3_secure/lifecycle/builder.rb +47 -0
  32. data/lib/s3_secure/lifecycle/list.rb +24 -0
  33. data/lib/s3_secure/lifecycle/remove.rb +28 -0
  34. data/lib/s3_secure/lifecycle/show.rb +40 -0
  35. data/lib/s3_secure/policy.rb +2 -0
  36. data/lib/s3_secure/policy/enforce.rb +3 -6
  37. data/lib/s3_secure/policy/list.rb +13 -17
  38. data/lib/s3_secure/policy/show.rb +8 -6
  39. data/lib/s3_secure/policy/unforce.rb +4 -7
  40. data/lib/s3_secure/remediate_all.rb +11 -0
  41. data/lib/s3_secure/summary/items.rb +0 -2
  42. data/lib/s3_secure/version.rb +1 -1
  43. data/lib/s3_secure/versioning.rb +29 -0
  44. data/lib/s3_secure/versioning/base.rb +4 -0
  45. data/lib/s3_secure/versioning/disable.rb +19 -0
  46. data/lib/s3_secure/versioning/enable.rb +19 -0
  47. data/lib/s3_secure/versioning/list.rb +24 -0
  48. data/lib/s3_secure/versioning/show.rb +27 -0
  49. data/s3-secure.gemspec +4 -2
  50. data/spec/lib/lifecycle/builder_spec.rb +85 -0
  51. metadata +48 -4
@@ -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
+ puts "Bucket ACL:"
5
+ pp bucket_acl_grants
6
+ puts "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,38 +2,6 @@ require "aws-sdk-s3"
2
2
 
3
3
  module S3Secure
4
4
  module AwsServices
5
- extend Memoist
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
- rescue Aws::STS::Errors::RegionDisabledException
26
- puts "ERROR: Fail to establish client connection to region #{region}".color(:red)
27
- raise
28
- end
29
- memoize :new_s3_regional_client
30
-
31
- # Generic s3 client. Will be configured to whatever region user has locally configured in ~/.aws/config
32
- # Used to call get_bucket_location to get each specific bucket's location.
33
- # Generally use the s3_regional_client instead of this.
34
- def s3_client
35
- Aws::S3::Client.new
36
- end
37
- memoize :s3_client
5
+ include S3
38
6
  end
39
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
@@ -3,6 +3,10 @@ module S3Secure
3
3
  class_option :verbose, 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,10 +15,24 @@ 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", "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
+
14
32
  desc "summary", "Summarize buckets"
15
33
  long_desc Help.text("summary")
16
- option :ssl, default: "any", desc: "filter for ssl enforcement. Examples: any, yes, no"
17
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"
18
36
  def summary
19
37
  Summary.new(options).run
20
38
  end
@@ -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
@@ -2,6 +2,8 @@ module S3Secure
2
2
  class Encryption < Command
3
3
  desc "list", "List bucket encryptions"
4
4
  long_desc Help.text("encryption/list")
5
+ option :format, desc: "Format options: #{CliFormat.formats.join(', ')}"
6
+ option :encryption, type: :boolean, desc: "Filter for encryption: all, true, false"
5
7
  def list
6
8
  List.new(options).run
7
9
  end
@@ -1,17 +1,13 @@
1
1
  class S3Secure::Encryption
2
2
  class Disable < Base
3
3
  def run
4
- @s3 = s3_regional_client(@bucket)
4
+ show = Show.new(@options)
5
5
 
6
- list = S3Secure::Encryption::List.new(@options)
7
- list.set_s3(@s3)
8
-
9
- rules = list.get_encryption_rules(@bucket)
10
- if rules
11
- @s3.delete_bucket_encryption(bucket: @bucket) # returns resp = #<struct Aws::EmptyStructure>
6
+ if show.enabled?
7
+ s3.delete_bucket_encryption(bucket: @bucket) # returns resp = #<struct Aws::EmptyStructure>
12
8
  puts "Bucket #{@bucket} encryption has been removed"
13
9
  else
14
- puts "WARN: Bucket #{@bucket} is not configured with encryption at the bucket level".color(:yellow)
10
+ puts "Bucket #{@bucket} is not configured with encryption at the bucket level"
15
11
  end
16
12
  end
17
13
  end
@@ -1,16 +1,12 @@
1
1
  class S3Secure::Encryption
2
2
  class Enable < Base
3
3
  def run
4
- @s3 = s3_regional_client(@bucket)
4
+ show = Show.new(@options)
5
5
 
6
- list = S3Secure::Encryption::List.new(@options)
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
8
  puts "Bucket #{@bucket} already has encryption rules:"
13
- puts rules.map(&:to_h)
9
+ puts show.rules.map(&:to_h)
14
10
  else
15
11
  # Set encryption rules
16
12
  # Ruby docs: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_encryption-instance_method
@@ -18,7 +14,7 @@ class S3Secure::Encryption
18
14
  #
19
15
  # put_bucket_encryption returns #<struct Aws::EmptyStructure>
20
16
  #
21
- @s3.put_bucket_encryption(
17
+ s3.put_bucket_encryption(
22
18
  bucket: @bucket,
23
19
  server_side_encryption_configuration: {
24
20
  rules: [rule]})
@@ -1,28 +1,24 @@
1
1
  class S3Secure::Encryption
2
2
  class List < Base
3
3
  def run
4
+ presenter = CliFormat::Presenter.new(@options)
5
+ presenter.header = ["Bucket", "Has Encryption?"]
6
+
4
7
  buckets.each do |bucket|
5
- @s3 = s3_regional_client(bucket)
6
- puts "Policy for bucket #{bucket.color(:green)}"
7
- encryption_rules = get_encryption_rules(bucket)
8
+ $stderr.puts "Getting encryption for bucket #{bucket.color(:green)}"
9
+ show = Show.new(bucket: bucket)
8
10
 
9
- if encryption_rules
10
- puts encryption_rules
11
+ row = [bucket, show.enabled?]
12
+ if @options[:encryption].nil?
13
+ presenter.rows << row # always show policy
14
+ elsif @options[:encryption]
15
+ presenter.rows << row if show.enabled? # only show if bucket has some encryption rules
11
16
  else
12
- puts "Bucket does not have bucket encryption enabled"
17
+ presenter.rows << row unless show.enabled? # only show if bucket doesnt have any encryption rules
13
18
  end
14
19
  end
15
- end
16
-
17
- def get_encryption_rules(bucket)
18
- resp = @s3.get_bucket_encryption(bucket: bucket)
19
- resp.server_side_encryption_configuration.rules # Aws::Xml::DefaultList object
20
- rescue Aws::S3::Errors::ServerSideEncryptionConfigurationNotFoundError
21
- end
22
20
 
23
- # Useful when calling List outside of the list CLI
24
- def set_s3(client)
25
- @s3 = client
21
+ presenter.show
26
22
  end
27
23
  end
28
24
  end
@@ -1,12 +1,6 @@
1
1
  class S3Secure::Encryption
2
2
  class Show < Base
3
3
  def run
4
- @s3 = s3_regional_client(@bucket)
5
-
6
- list = S3Secure::Encryption::List.new(@options)
7
- list.set_s3(@s3)
8
-
9
- rules = list.get_encryption_rules(@bucket)
10
4
  if rules
11
5
  puts "Bucket #{@bucket} is configured with these encryption rules:"
12
6
  puts rules.map(&:to_h)
@@ -14,5 +8,16 @@ class S3Secure::Encryption
14
8
  puts "Bucket #{@bucket} is not configured with encryption at the bucket level"
15
9
  end
16
10
  end
11
+
12
+ def enabled?
13
+ !!(rules && !rules.empty?)
14
+ end
15
+
16
+ def rules
17
+ resp = s3.get_bucket_encryption(bucket: @bucket)
18
+ resp.server_side_encryption_configuration.rules # Aws::Xml::DefaultList object
19
+ rescue Aws::S3::Errors::ServerSideEncryptionConfigurationNotFoundError
20
+ end
21
+ memoize :rules
17
22
  end
18
23
  end
@@ -0,0 +1,14 @@
1
+ There are some supported batch commands:
2
+
3
+ s3-secure batch encryption enable FILE.txt
4
+ s3-secure batch encryption disable FILE.txt
5
+ s3-secure batch policy enforce_ssl FILE.txt
6
+ s3-secure batch policy unforce_ssl FILE.txt
7
+
8
+ The format of FILE.txt is a list of bucket names separated by newlines. Example:
9
+
10
+ buckets.txt:
11
+
12
+ my-bucket-1
13
+ my-bucket-2
14
+
@@ -0,0 +1,5 @@
1
+ ## Examples
2
+
3
+ s3-secure encryption list # shows all buckets with and without polices
4
+ s3-secure encryption list --encryption # only shows buckets with encryption
5
+ s3-secure encryption list --no-encryption # only shows buckets without encryption
@@ -0,0 +1,13 @@
1
+ ## Example
2
+
3
+ $ s3-secure lifecycle add a-test-bucket-in-us-east-1
4
+ Added lifecycle policy to bucket a-test-bucket-in-us-east-1
5
+ $
6
+
7
+ By default, the add command will only add a lifecycle policy if you none exists.
8
+
9
+ It may be useful to test adding an additional lifecycle policy, for this you can use both the `--additive` and `--prefix` options. Note, you must make sure that the lifecycle policies can work together. For example, they must have different prefixes.
10
+
11
+ $ s3-secure lifecycle add a-test-bucket-in-us-east-1 --additive --prefix /foo
12
+ Added lifecycle policy to bucket a-test-bucket-in-us-east-1
13
+ $
@@ -0,0 +1,22 @@
1
+ ## Examples
2
+
3
+ $ s3-secure lifecycle list
4
+ +----------------------------+----------------------+
5
+ | Bucket | Has Lifecycle Rules? |
6
+ +----------------------------+----------------------+
7
+ | a-test-bucket-in-us-east-1 | false |
8
+ | a-test-bucket-in-us-west-1 | true |
9
+ +----------------------------+----------------------+
10
+ $ s3-secure lifecycle list --lifecycle true
11
+ +----------------------------+----------------------+
12
+ | Bucket | Has Lifecycle Rules? |
13
+ +----------------------------+----------------------+
14
+ | a-test-bucket-in-us-west-1 | true |
15
+ +----------------------------+----------------------+
16
+ $ s3-secure lifecycle list --lifecycle false
17
+ +----------------------------+----------------------+
18
+ | Bucket | Has Lifecycle Rules? |
19
+ +----------------------------+----------------------+
20
+ | a-test-bucket-in-us-east-1 | false |
21
+ +----------------------------+----------------------+
22
+ $