terraspace_plugin_aws 0.3.1 → 0.3.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 855a5c7166b24f8881c2d735edd0c252cefd6d0175a9a0f1156090ca08a11e5d
4
- data.tar.gz: be95c5a77b116c62dac152ec6f8ec9cfb5b68ad372475373823508472bca3566
3
+ metadata.gz: c20f17d758fbb6924ceece1c1bbc16ec17e3eba0fa8a0a6fa4d644b9875579b6
4
+ data.tar.gz: 73b7f625a5f84f7252206717523807c7607ac4a4fe6f6572cfd01e7110641b38
5
5
  SHA512:
6
- metadata.gz: 12ff9c7c0fcc8b10d82385855b5a5724392729ef79254cad41fce51b0ddab0d3ec17a927b08da1e071adde23e1c8e1289e48d32f7948753675b6d0fd680596c7
7
- data.tar.gz: 717dffbad397bb5fd3953739060e5e2ad7bb6d90f842c13ab6f0fb00f5f3cf1b82357ed401b118a2ddde584c9dd6ccdda0712cce493fd2ec0be48433b35f52ab
6
+ metadata.gz: 39978c0db0055dba8dc59246b9da3dd20c469daf36805526f4b2c0c051425dd6dbf0be054812de9e7ae0c32b31657222225ae039915ddc3ef11aab657d9adbb9
7
+ data.tar.gz: ff11fee1dec2508d941dacd4f727ad362802285aca20ac2b0aa9cc87a034e0d287f9f10db3816e330958025e608fdeff45720b543047f1d3fe5fe52f5f305fb1
data/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/).
5
5
 
6
+ ## [0.3.5] - 2021-12-30
7
+ - [#15](https://github.com/boltops-tools/terraspace_plugin_aws/pull/15) block public access support
8
+ - [#16](https://github.com/boltops-tools/terraspace_plugin_aws/pull/16) tagging support for s3 bucket and dynamodb table
9
+
10
+ ## [0.3.4] - 2021-12-30
11
+ - [#13](https://github.com/boltops-tools/terraspace_plugin_aws/pull/13) check aws setup and provide friendly message
12
+ - [#14](https://github.com/boltops-tools/terraspace_plugin_aws/pull/14) fix aws_secret helper
13
+
14
+ ## [0.3.3] - 2021-12-14
15
+ - [#10](https://github.com/boltops-tools/terraspace_plugin_aws/pull/10) implement expand_string? to not expand aws arn values
16
+
17
+ ## [0.3.2] - 2021-12-14
18
+ - [#9](https://github.com/boltops-tools/terraspace_plugin_aws/pull/9) support separate aws account for s3 backend bucket
19
+
6
20
  ## [0.3.1] - 2021-12-14
7
21
  - [#8](https://github.com/boltops-tools/terraspace_plugin_aws/pull/8) use region configured in the backend.tf for the s3 client
8
22
 
@@ -0,0 +1,98 @@
1
+ module TerraspacePluginAws::Clients
2
+ module Options
3
+ private
4
+ def client_options
5
+ return {} unless @info # aws_secret helper wont have @info
6
+ if @info['role_arn']
7
+ client_assume_role_options
8
+ else
9
+ client_default_options
10
+ end
11
+ end
12
+
13
+ # Typically, aws sdk client options are inferred from the user environment unless set in the backend.tf
14
+ #
15
+ # terraform s3 backend assume role configuration: https://www.terraform.io/docs/language/settings/backends/s3.html
16
+ #
17
+ # assume_role_duration_seconds - (Optional) Number of seconds to restrict the assume role session duration.
18
+ # assume_role_policy - (Optional) IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.
19
+ # assume_role_policy_arns - (Optional) Set of Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.
20
+ # assume_role_tags - (Optional) Map of assume role session tags.
21
+ # assume_role_transitive_tag_keys - (Optional) Set of assume role session tag keys to pass to any subsequent sessions.
22
+ # external_id - (Optional) External identifier to use when assuming the role.
23
+ # role_arn - (Optional) Amazon Resource Name (ARN) of the IAM Role to assume.
24
+ # session_name - (Optional) Session name to use when assuming the role.
25
+ #
26
+ # ruby sdk: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/AssumeRoleCredentials.html
27
+ #
28
+ # :role_arn (required, String)
29
+ # :role_session_name (required, String)
30
+ # :policy (String)
31
+ # :duration_seconds (Integer)
32
+ # :external_id (String)
33
+ # :client (STS::Client)
34
+ #
35
+ def client_assume_role_options
36
+ whitelist = %w[
37
+ assume_role_duration_seconds
38
+ assume_role_policy
39
+ session_name
40
+ external_id
41
+ role_arn
42
+ ]
43
+ assume_role_config = @info.slice(*whitelist)
44
+ # not supported?
45
+ # assume_role_policy_arns
46
+ # assume_role_tags
47
+ # assume_role_transitive_tag_keys
48
+ # already matches
49
+ # external_id
50
+ # role_arn
51
+ # rest needs to be mapped
52
+ map = {
53
+ 'assume_role_duration_seconds' => 'duration_seconds',
54
+ 'assume_role_policy' => 'policy',
55
+ 'session_name' => 'role_session_name',
56
+ }
57
+ map.each do |terraform_key, ruby_sdk_key|
58
+ v = assume_role_config.delete(terraform_key)
59
+ assume_role_config[ruby_sdk_key] = v if v
60
+ end
61
+ assume_role_config.symbolize_keys! # ruby sdk expects symbols for keys
62
+ assume_role_config[:role_session_name] ||= [ENV['C9_USER'] || ENV['USER'], 'session'].compact.join('-') # session name is required for the ruby sdk
63
+ role_credentials = Aws::AssumeRoleCredentials.new(assume_role_config)
64
+ {credentials: role_credentials}
65
+ end
66
+
67
+ # terraform s3 backend configuration: https://www.terraform.io/docs/language/settings/backends/s3.html
68
+ #
69
+ # access_key - (Optional) AWS access key. If configured, must also configure secret_key. This can also be sourced from the AWS_ACCESS_KEY_ID environment variable, AWS shared credentials file (e.g. ~/.aws/credentials), or AWS shared configuration file (e.g. ~/.aws/config).
70
+ # secret_key - (Optional) AWS access key. If configured, must also configure access_key. This can also be sourced from the AWS_SECRET_ACCESS_KEY environment variable, AWS shared credentials file (e.g. ~/.aws/credentials), or AWS shared configuration file (e.g. ~/.aws/config).
71
+ #
72
+ # ruby sdk: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Credentials.html
73
+ #
74
+ # access_key_id (String)
75
+ # secret_access_key (String)
76
+ # session_token (String) (defaults to: nil) — (nil)
77
+ #
78
+ def client_default_options
79
+ whitelist = %w[
80
+ access_key_id
81
+ secret_access_key
82
+ session_token
83
+ profile
84
+ ]
85
+ options = @info.slice(*whitelist)
86
+ options.symbolize_keys! # ruby sdk expects symbols for keys
87
+ client_region_option.merge(options)
88
+ end
89
+
90
+ def client_region_option
91
+ if @info['region']
92
+ {region: @info['region']}
93
+ else
94
+ {}
95
+ end
96
+ end
97
+ end
98
+ end
@@ -6,6 +6,7 @@ require "aws-sdk-ssm"
6
6
  module TerraspacePluginAws
7
7
  module Clients
8
8
  extend Memoist
9
+ include Options
9
10
 
10
11
  def s3
11
12
  Aws::S3::Client.new(client_options)
@@ -22,18 +23,14 @@ module TerraspacePluginAws
22
23
  end
23
24
  memoize :ssm
24
25
 
26
+ def sts
27
+ Aws::STS::Client.new(client_options)
28
+ end
29
+ memoize :sts
30
+
25
31
  def dynamodb
26
32
  Aws::DynamoDB::Client.new(client_options)
27
33
  end
28
34
  memoize :dynamodb
29
-
30
- # Typically inferred from AWS_REGION unless set in the backend.tf
31
- def client_options
32
- if @info['region']
33
- {region: @info['region']}
34
- else
35
- {}
36
- end
37
- end
38
35
  end
39
36
  end
@@ -2,6 +2,27 @@ require "s3-secure"
2
2
 
3
3
  class TerraspacePluginAws::Interfaces::Backend::Bucket
4
4
  module Secure
5
+ # Why the retry logic?
6
+ #
7
+ # When using profile or role_arn in the terraform backend it the ruby aws sdk
8
+ # assumes the profile or role.
9
+ # In doing so, it errors when the s3-secure library calls s3_client.get_bucket_location
10
+ #
11
+ # https://github.com/boltops-tools/s3-secure/blob/d2c8e9eba745a75d094a3c566bd5fe47476d3638/lib/s3_secure/aws_services/s3.rb#L43
12
+ #
13
+ # Here's an example stack trace of the error:
14
+ #
15
+ # https://gist.github.com/tongueroo/dd74b67c17433c6f8dd890225104aef9
16
+ #
17
+ # Unsure if this is a terraform backend interfering with the ruby sdk thing (unlikely)
18
+ # Or if it's a general AWS sdk thing.
19
+ # Or if it's how I'm calling the sdk and initializing the client. Maybe an initializing the client early on and it caches it.
20
+ # Unsure. But using this hack instead because life's short.
21
+ #
22
+ # Throwing the retry logic in here fixes the issue. This only happens the when the bucket is brand new.
23
+ # Limiting the retry to only a single attempt.
24
+ #
25
+ @@retries = 0
5
26
  def secure(bucket)
6
27
  c = TerraspacePluginAws::Interfaces::Config.instance.config.s3
7
28
  options = {bucket: bucket, quiet: true}
@@ -10,6 +31,10 @@ class TerraspacePluginAws::Interfaces::Backend::Bucket
10
31
  S3Secure::Versioning::Enable.new(options).run if c.versioning
11
32
  S3Secure::Lifecycle::Add.new(options).run if c.lifecycle
12
33
  S3Secure::AccessLogs::Enable.new(options).run if c.access_logging
34
+ S3Secure::PublicAccess::Block.new(options).run if c.block_public_access
35
+ rescue Aws::S3::Errors::AccessDenied => e
36
+ @@retries += 1
37
+ retry unless @@retries > 1
13
38
  end
14
39
  end
15
40
  end
@@ -0,0 +1,44 @@
1
+ class TerraspacePluginAws::Interfaces::Backend::Bucket
2
+ class Tagging
3
+ include TerraspacePluginAws::Clients
4
+ include TerraspacePluginAws::Logging
5
+
6
+ def initialize(bucket)
7
+ @bucket = bucket
8
+ end
9
+
10
+ def tag
11
+ return if tagging.nil? || tagging[:tag_set].empty? # safeguard: dont overwrite current tags
12
+ s3.put_bucket_tagging(bucket: @bucket, tagging: tagging)
13
+ end
14
+
15
+ # Merges existing tag_set structure so always appends tags, wont remove tags.
16
+ # This behavior is consistent with the dynamodb tagging.
17
+ #
18
+ # Example return:
19
+ #
20
+ # {
21
+ # tag_set: [
22
+ # { key: "Key1", value: "Value1" },
23
+ # { key: "Key2", value: "Value2" },
24
+ # ],
25
+ # }
26
+ #
27
+ def tagging
28
+ c = TerraspacePluginAws::Interfaces::Config.instance.config
29
+ tags = !c.s3.tags.empty? ? c.s3.tags : c.tags
30
+ tag_set = tags.map do |k,v|
31
+ {key: k.to_s, value: v}
32
+ end
33
+ return if tag_set == existing_tagging[:tag_set] # return nil so we can avoid the put_bucket_tagging call
34
+ tag_set += existing_tagging[:tag_set]
35
+ { tag_set: tag_set }
36
+ end
37
+
38
+ def existing_tagging
39
+ s3.get_bucket_tagging(bucket: @bucket).to_h
40
+ rescue Aws::S3::Errors::NoSuchTagSet
41
+ {tag_set: []} # normalize return structure
42
+ end
43
+ end
44
+ end
@@ -10,12 +10,14 @@ class TerraspacePluginAws::Interfaces::Backend
10
10
  end
11
11
  if exist?(bucket)
12
12
  logger.debug "Bucket already exist: #{bucket}"
13
- c = TerraspacePluginAws::Interfaces::Config.instance.config.s3
14
- secure(bucket) if c.secure_existing
13
+ c = TerraspacePluginAws::Interfaces::Config.instance.config
14
+ secure(bucket) if c.s3.secure_existing
15
+ tag(bucket) if c.tag_existing
15
16
  else
16
17
  logger.info "Creating bucket: #{bucket}"
17
18
  s3.create_bucket(bucket: bucket)
18
19
  secure(bucket)
20
+ tag(bucket)
19
21
  end
20
22
  end
21
23
 
@@ -30,5 +32,9 @@ class TerraspacePluginAws::Interfaces::Backend
30
32
  logger.error "Bucket might be owned by someone else or is on another one of your AWS accounts."
31
33
  exit 1
32
34
  end
35
+
36
+ def tag(bucket)
37
+ Tagging.new(@info["bucket"]).tag
38
+ end
33
39
  end
34
40
  end
@@ -0,0 +1,15 @@
1
+ class TerraspacePluginAws::Interfaces::Backend
2
+ class Setup < Base
3
+ def check!
4
+ sts.get_caller_identity
5
+ rescue Aws::Errors::MissingCredentialsError => e
6
+ logger.info "ERROR: #{e.class}: #{e.message}".color(:red)
7
+ logger.info <<~EOL
8
+ It doesnt look like AWS credentials and access has been setup.
9
+ Please double check the AWS credentials setup.
10
+ IE: ~/.aws/config and the AWS_PROFILE env variable.
11
+ EOL
12
+ exit 1
13
+ end
14
+ end
15
+ end
@@ -6,6 +6,8 @@ class TerraspacePluginAws::Interfaces::Backend
6
6
 
7
7
  if exist?(table)
8
8
  logger.debug "Table already exist: #{table}"
9
+ c = TerraspacePluginAws::Interfaces::Config.instance.config
10
+ tag_existing(table) if c.tag_existing
9
11
  else
10
12
  logger.info "Creating dynamodb table: #{table}"
11
13
  create_table(table)
@@ -36,6 +38,7 @@ class TerraspacePluginAws::Interfaces::Backend
36
38
  table_name: name,
37
39
  }
38
40
  secure(definition)
41
+ tag(definition)
39
42
  definition
40
43
  end
41
44
 
@@ -64,6 +67,29 @@ class TerraspacePluginAws::Interfaces::Backend
64
67
  definition
65
68
  end
66
69
 
70
+ def tag(definition)
71
+ definition[:tags] = tags unless tags.empty?
72
+ end
73
+
74
+ def tag_existing(table_name)
75
+ return if tags.empty?
76
+ resp = dynamodb.describe_table(table_name: table_name)
77
+ # Always appends tags, wont remove tags.
78
+ dynamodb.tag_resource(
79
+ resource_arn: resp.table.table_arn,
80
+ tags: tags
81
+ )
82
+ end
83
+
84
+ def tags
85
+ c = TerraspacePluginAws::Interfaces::Config.instance.config
86
+ tags = !c.dynamodb.tags.empty? ? c.dynamodb.tags : c.tags
87
+ # Note there is no map! method for Hash
88
+ tags = tags.map do |k,v|
89
+ {key: k.to_s, value: v}
90
+ end
91
+ end
92
+
67
93
  def exist?(name)
68
94
  dynamodb.describe_table(table_name: name)
69
95
  true # table exist
@@ -6,6 +6,7 @@ module TerraspacePluginAws::Interfaces
6
6
  def call
7
7
  return unless TerraspacePluginAws.config.auto_create
8
8
 
9
+ Setup.new(@info).check!
9
10
  Bucket.new(@info).create
10
11
  Table.new(@info).create
11
12
  end
@@ -14,19 +14,24 @@ module TerraspacePluginAws::Interfaces
14
14
  c = ActiveSupport::OrderedOptions.new
15
15
 
16
16
  c.auto_create = true
17
+ c.tags = {} # can set tags for both s3 bucket and dynamodb table with this config
18
+ c.tag_existing = true
17
19
 
18
20
  c.s3 = ActiveSupport::OrderedOptions.new
21
+ c.s3.access_logging = false
22
+ c.s3.block_public_access = true
19
23
  c.s3.encryption = true
20
24
  c.s3.enforce_ssl = true
21
- c.s3.versioning = true
22
25
  c.s3.lifecycle = true
23
- c.s3.access_logging = false
26
+ c.s3.versioning = true
24
27
  c.s3.secure_existing = false # run the security controls on existing buckets. by default, only run on newly created bucket the first time
28
+ c.s3.tags = {} # cannot assign to c.tags here because it's a copy
25
29
 
26
30
  c.dynamodb = ActiveSupport::OrderedOptions.new
27
31
  c.dynamodb.encryption = true
28
32
  c.dynamodb.kms_master_key_id = nil
29
33
  c.dynamodb.sse_type = "KMS"
34
+ c.dynamodb.tags = {} # cannot assign to c.tags here because it's a copy
30
35
 
31
36
  c
32
37
  end
@@ -10,5 +10,9 @@ module TerraspacePluginAws::Interfaces
10
10
  def aws_data
11
11
  $__aws_data ||= AwsData.new
12
12
  end
13
+
14
+ def expand_string?(string)
15
+ !string.include?("arn:")
16
+ end
13
17
  end
14
18
  end
@@ -1,3 +1,3 @@
1
1
  module TerraspacePluginAws
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.5"
3
3
  end
@@ -28,6 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency "aws-sdk-ssm"
29
29
  spec.add_dependency "aws_data"
30
30
  spec.add_dependency "memoist"
31
- spec.add_dependency "s3-secure"
31
+ spec.add_dependency "s3-secure", "~> 0.6.1"
32
32
  spec.add_dependency "zeitwerk"
33
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terraspace_plugin_aws
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-14 00:00:00.000000000 Z
11
+ date: 2021-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb
@@ -98,16 +98,16 @@ dependencies:
98
98
  name: s3-secure
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: 0.6.1
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: 0.6.1
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: zeitwerk
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -167,10 +167,13 @@ files:
167
167
  - lib/terraspace_plugin_aws.rb
168
168
  - lib/terraspace_plugin_aws/autoloader.rb
169
169
  - lib/terraspace_plugin_aws/clients.rb
170
+ - lib/terraspace_plugin_aws/clients/options.rb
170
171
  - lib/terraspace_plugin_aws/interfaces/backend.rb
171
172
  - lib/terraspace_plugin_aws/interfaces/backend/base.rb
172
173
  - lib/terraspace_plugin_aws/interfaces/backend/bucket.rb
173
174
  - lib/terraspace_plugin_aws/interfaces/backend/bucket/secure.rb
175
+ - lib/terraspace_plugin_aws/interfaces/backend/bucket/tagging.rb
176
+ - lib/terraspace_plugin_aws/interfaces/backend/setup.rb
174
177
  - lib/terraspace_plugin_aws/interfaces/backend/table.rb
175
178
  - lib/terraspace_plugin_aws/interfaces/config.rb
176
179
  - lib/terraspace_plugin_aws/interfaces/decorator.rb