kubes_aws 0.0.0.beta → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19d8e8171c4111b246797f27545db16dfcf6f4f8fcf97586e193b82e40e58e47
4
- data.tar.gz: 26326b10b3ce96e90468db2917930d3e4c93e5ca58dfc8cb713a3ef6a15ff99f
3
+ metadata.gz: 216f223229ec7e134e52a869f36507518c8376d768e3ae72ec5b557c7e1a31a7
4
+ data.tar.gz: 3fd50925b7906fe8c18f6b6e5c20065e895097aeaa57926093cf57ad1b445121
5
5
  SHA512:
6
- metadata.gz: 5b97552f1de27b0abfe4342e03f8206c931304dede4b4ca357b616dc7c80882c5fe04754146431d8433dcf11cf2b2606daba4b70e3a8dd8f3dbd9c373fe3a6d8
7
- data.tar.gz: 86eab18a16e46413767866558f17ff743d101fa58e4e03ab3f982194e31d2810f55cb4a61d9c908cb26bd84681f4b565a3ca2a9da14b5fc7f245813f03564f49
6
+ metadata.gz: ef86d09153f32cb9bd6ae87eb905ef4de3a07ffa86abae0ed039655ee2305bae228de3d895c7d77cc3112fa2166ffa25fa4632163bd17969034947b067d30446
7
+ data.tar.gz: 4437ae6bdc30d96afd98b0261893d52a4c195eeb135b14b8823d6024d601743f431eaefaccf787a90cb1600c6eedc405d59c8f94f958c76ef8109be39b6b6243
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
+
6
+ ## [0.1.0]
7
+ - Initial release.
data/Gemfile CHANGED
@@ -3,5 +3,5 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in kubes_aws.gemspec
4
4
  gemspec
5
5
 
6
- gem "rake"
7
- gem "rspec"
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/README.md CHANGED
@@ -1,10 +1,14 @@
1
- # Kubes AWS Helpers Library
1
+ # Kubes AWS
2
2
 
3
- Starter example.
3
+ [![Gem Version](https://badge.fury.io/rb/kubes_aws.png)](http://badge.fury.io/rb/kubes_aws)
4
+
5
+ [![BoltOps Badge](https://img.boltops.com/boltops/badges/boltops-badge.png)](https://www.boltops.com)
6
+
7
+ [Kubes](https://kubes.guru) Library with AWS helpers.
4
8
 
5
9
  ## Usage
6
10
 
7
- ...
11
+ For more detailed usage instructions refer to the [Kubes Helpers docs](https://kubes.guru/docs/helpers/aws/).
8
12
 
9
13
  ## Contributing
10
14
 
@@ -21,4 +21,13 @@ Gem::Specification.new do |spec|
21
21
  spec.bindir = "exe"
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency "activesupport"
26
+ spec.add_dependency "aws-sdk-eks"
27
+ spec.add_dependency "aws-sdk-iam"
28
+ spec.add_dependency "aws-sdk-secretsmanager"
29
+ spec.add_dependency "aws-sdk-ssm"
30
+ spec.add_dependency "aws_data"
31
+ spec.add_dependency "memoist"
32
+ spec.add_dependency "zeitwerk"
24
33
  end
@@ -1,6 +1,20 @@
1
1
  require "kubes_aws/version"
2
+ require "logger"
3
+
4
+ require "kubes_aws/autoloader"
5
+ KubesAws::Autoloader.setup
2
6
 
3
7
  module KubesAws
4
8
  class Error < StandardError; end
5
- # Your code goes here...
9
+
10
+ @@logger = nil
11
+ def logger
12
+ @@logger ||= Kubes.logger
13
+ end
14
+
15
+ def logger=(v)
16
+ @@logger = v
17
+ end
18
+
19
+ extend self
6
20
  end
@@ -0,0 +1,22 @@
1
+ require "zeitwerk"
2
+
3
+ module KubesAws
4
+ class Autoloader
5
+ class Inflector < Zeitwerk::Inflector
6
+ def camelize(basename, _abspath)
7
+ map = { cli: "CLI", ssm: "SSM", version: "VERSION" }
8
+ map[basename.to_sym] || super
9
+ end
10
+ end
11
+
12
+ class << self
13
+ def setup
14
+ loader = Zeitwerk::Loader.new
15
+ loader.inflector = Inflector.new
16
+ lib = File.expand_path("../", __dir__)
17
+ loader.push_dir(lib)
18
+ loader.setup
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ require "aws-sdk-eks"
2
+ require "aws-sdk-iam"
3
+
4
+ module KubesAws
5
+ module AwsServices
6
+ extend Memoist
7
+
8
+ def eks
9
+ Aws::EKS::Client.new
10
+ end
11
+ memoize :eks
12
+
13
+ def iam
14
+ Aws::IAM::Client.new
15
+ end
16
+ memoize :iam
17
+
18
+ def secrets
19
+ Aws::SecretsManager::Client.new
20
+ end
21
+ memoize :secrets
22
+
23
+ def ssm
24
+ Aws::SSM::Client.new
25
+ end
26
+ memoize :ssm
27
+ end
28
+ end
@@ -0,0 +1,141 @@
1
+ require "active_support/core_ext/string"
2
+ require "aws_data"
3
+ require "json"
4
+
5
+ module KubesAws
6
+ class IamRole
7
+ extend Memoist
8
+ include AwsServices
9
+ include Logging
10
+ include Prebaked
11
+
12
+ # public method to keep: role_name
13
+ attr_reader :role_name
14
+ def initialize(app:, cluster:, namespace:nil, managed_policies: [], inline_policies: [], role_name: nil, ksa: nil)
15
+ @app, @cluster, @managed_policies, @inline_policies = app, cluster, managed_policies, inline_policies
16
+
17
+ # conventional names
18
+ @ksa = ksa || @app # convention: app
19
+ @namespace = namespace || "#{@app}-#{Kubes.env}" # convention: app-env
20
+ @role_name = role_name || "#{@app}-#{Kubes.env}" # convention: app-env
21
+ end
22
+
23
+ def call
24
+ create_open_id_connect_provider
25
+ create_iam_role
26
+ add_mananged_policies
27
+ add_inline_policies
28
+ end
29
+
30
+ def add_inline_policies
31
+ @inline_policies.each do |policy|
32
+ params = normalize_inline_policy(policy)
33
+ iam.put_role_policy(params)
34
+ end
35
+ end
36
+
37
+ # resp = client.put_role_policy(
38
+ # policy_document: "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"s3:*\",\"Resource\":\"*\"}}",
39
+ # policy_name: "S3AccessPolicy",
40
+ # role_name: "S3Access",
41
+ # )
42
+ def normalize_inline_policy(policy)
43
+ prebaked = prebaked_policies[policy]
44
+ policy = prebaked if prebaked
45
+
46
+ policy_document = policy[:policy_document]
47
+ policy[:policy_document] = JSON.dump(policy_document) if policy_document.is_a?(Hash)
48
+ policy[:role_name] = @role_name
49
+ policy
50
+ end
51
+
52
+ def create_open_id_connect_provider
53
+ open_id = OpenId.new(@cluster)
54
+ open_id.create_provider
55
+ end
56
+
57
+ def create_iam_role
58
+ return if role_exist?
59
+ iam.create_role(
60
+ role_name: @role_name,
61
+ assume_role_policy_document: trust_policy,
62
+ )
63
+ logger.debug "Created IAM Role #{@role_name}"
64
+ end
65
+
66
+ def add_mananged_policies
67
+ @managed_policies.each do |policy|
68
+ policy_arn = normalize_managed_policy(policy)
69
+ iam.attach_role_policy(
70
+ role_name: @role_name,
71
+ policy_arn: policy_arn,
72
+ )
73
+ end
74
+ logger.debug "IAM Policies added to #{@role_name}"
75
+ end
76
+
77
+ # AmazonS3ReadOnlyAccess => arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
78
+ def normalize_managed_policy(policy)
79
+ if policy.include?("arn:")
80
+ policy
81
+ else
82
+ "arn:aws:iam::aws:policy/#{policy}"
83
+ end
84
+ end
85
+
86
+ def role_exist?
87
+ iam.get_role(role_name: @role_name)
88
+ true
89
+ rescue Aws::IAM::Errors::NoSuchEntity
90
+ false
91
+ end
92
+
93
+ # public method to keep: arn
94
+ def arn
95
+ "arn:aws:iam::#{aws_account}:role/#{@role_name}"
96
+ end
97
+
98
+ # public method to keep: aws_account
99
+ def aws_account
100
+ aws.account
101
+ end
102
+
103
+ def trust_policy
104
+ issuer_host = issuer_url.sub('https://','')
105
+ provider_arn = "arn:aws:iam::#{aws_account}:oidc-provider/#{issuer_host}"
106
+ <<~JSON
107
+ {
108
+ "Version": "2012-10-17",
109
+ "Statement": [
110
+ {
111
+ "Effect": "Allow",
112
+ "Principal": {
113
+ "Federated": "#{provider_arn}"
114
+ },
115
+ "Action": "sts:AssumeRoleWithWebIdentity",
116
+ "Condition": {
117
+ "StringEquals": {
118
+ "#{issuer_host}:sub": "system:serviceaccount:#{@namespace}:#{@ksa}"
119
+ }
120
+ }
121
+ }
122
+ ]
123
+ }
124
+ JSON
125
+ end
126
+
127
+ def issuer_url
128
+ resp = eks.describe_cluster(name: @cluster)
129
+ resp.cluster.identity.oidc.issuer
130
+ end
131
+ memoize :issuer_url
132
+
133
+ def aws
134
+ AwsData.new
135
+ end
136
+ memoize :aws
137
+
138
+ # useful to store data used later
139
+ class_attribute :data, :role_arn
140
+ end
141
+ end
@@ -0,0 +1,27 @@
1
+ class KubesAws::IamRole
2
+ module Prebaked
3
+ def prebaked_policies
4
+ {
5
+ secrets_read_only: secrets_read_only
6
+ }
7
+ end
8
+
9
+ def secrets_read_only
10
+ {
11
+ policy_document: {
12
+ Version: "2012-10-17",
13
+ Statement: {
14
+ Effect: "Allow",
15
+ Action: [
16
+ "secretsmanager:Describe*",
17
+ "secretsmanager:Get*",
18
+ "secretsmanager:List*"
19
+ ],
20
+ Resource: "*"
21
+ }
22
+ },
23
+ policy_name: "SecretsReadOnly",
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ module KubesAws
2
+ module Logging
3
+ def logger
4
+ KubesAws.logger
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ require "aws-sdk-iam"
2
+ require "aws_data"
3
+ require "openssl"
4
+
5
+ module KubesAws
6
+ class OpenId
7
+ extend Memoist
8
+ include AwsServices
9
+ include Logging
10
+
11
+ def initialize(cluster)
12
+ @cluster = cluster
13
+ end
14
+
15
+ # Method is idempotent
16
+ def create_provider
17
+ fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
18
+ iam.create_open_id_connect_provider(
19
+ url: issuer_url,
20
+ thumbprint_list: [fingerprint],
21
+ client_id_list: ["sts.amazonaws.com"]
22
+ )
23
+ rescue Aws::IAM::Errors::EntityAlreadyExists => e
24
+ logger.debug "#{e.class}: #{e.message}"
25
+ logger.debug "Open ID Provider already exists"
26
+ end
27
+
28
+ def issuer_url
29
+ resp = eks.describe_cluster(name: @cluster)
30
+ resp.cluster.identity.oidc.issuer
31
+ end
32
+ memoize :issuer_url
33
+
34
+ # https://stackoverflow.com/questions/34601260/using-ruby-openssl-to-download-and-read-certificates
35
+ def cert
36
+ uri = URI(issuer_url)
37
+ ctx = OpenSSL::SSL::SSLContext.new
38
+ sock = TCPSocket.new(uri.host, 443)
39
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
40
+ ssl.connect
41
+ ssl.peer_cert_chain.last
42
+ end
43
+ memoize :cert
44
+
45
+ def aws_region
46
+ AwsData.new.region
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ require "aws-sdk-secretsmanager"
2
+
3
+ module KubesAws
4
+ class Secrets
5
+ include AwsServices
6
+
7
+ def initialize(upcase: false, base64: false, prefix: nil, filters: [])
8
+ @upcase, @base64, @filters = upcase, base64, filters
9
+ @prefix = ENV['AWS_SECRET_PREFIX'] || prefix # IE: prefix: demo/dev/
10
+ end
11
+
12
+ def call
13
+ items.each do |item|
14
+ next unless item.name.include?(@prefix) if @prefix
15
+
16
+ secret_value = secrets.get_secret_value(secret_id: item.name)
17
+ value = secret_value.secret_string
18
+ value = Base64.strict_encode64(value).strip if @base64
19
+
20
+ key = item.name
21
+ key = key.sub(@prefix,'') if @prefix
22
+ key = key.upcase if @upcase
23
+ self.class.data[key] = value
24
+ end
25
+ end
26
+
27
+ # Returns flattened lazy Enumerator
28
+ def items
29
+ Enumerator.new do |y|
30
+ next_token = nil
31
+ loop do
32
+ args = {max_results: PAGE_SIZE, sort_order: "asc"}
33
+ args[:next_token] = next_token if next_token
34
+ args.merge!(filters: @filters)
35
+
36
+ resp = secrets.list_secrets(args)
37
+
38
+ items = resp.secret_list
39
+ next_token = resp.next_token
40
+
41
+ y.yield(items, resp) # also provided the original resp always in case it is useful
42
+ break unless next_token
43
+ end
44
+ end.lazy.flat_map { |v| v }
45
+ end
46
+
47
+ PAGE_SIZE = 20
48
+
49
+ def data
50
+ self.class.data
51
+ end
52
+
53
+ class_attribute :data
54
+ self.data = {}
55
+ end
56
+ end
@@ -0,0 +1,59 @@
1
+ require "aws-sdk-ssm"
2
+
3
+ module KubesAws
4
+ class SSM
5
+ include AwsServices
6
+
7
+ def initialize(upcase: false, base64: false, prefix: nil, filters: [])
8
+ @upcase, @base64, @filters = upcase, base64, filters
9
+ @prefix = ENV['AWS_SSM_PREFIX'] || prefix # IE: prefix: /demo/dev/
10
+ end
11
+
12
+ def call
13
+ items.each do |item|
14
+ next unless item.name.include?(@prefix)
15
+
16
+ resp = ssm.get_parameter(name: item.name, with_decryption: true)
17
+ parameter = resp.parameter
18
+
19
+ key = parameter.name.sub(@prefix,'')
20
+ value = parameter.value
21
+ value = Base64.strict_encode64(value).strip if @base64
22
+
23
+ key = key.upcase if @upcase
24
+ self.class.data[key] = value
25
+ end
26
+ end
27
+
28
+ # Returns flattened lazy Enumerator
29
+ def items
30
+ Enumerator.new do |y|
31
+ next_token = nil
32
+ loop do
33
+ args = {max_results: PAGE_SIZE}
34
+ args[:next_token] = next_token if next_token
35
+ args.merge!(parameter_filters: @filters)
36
+
37
+ resp = ssm.get_parameters_by_path(
38
+ path: @prefix,
39
+ )
40
+
41
+ items = resp.parameters
42
+ next_token = resp.next_token
43
+
44
+ y.yield(items, resp) # also provided the original resp always in case it is useful
45
+ break unless next_token
46
+ end
47
+ end.lazy.flat_map { |v| v }
48
+ end
49
+
50
+ PAGE_SIZE = 1
51
+
52
+ def data
53
+ self.class.data
54
+ end
55
+
56
+ class_attribute :data
57
+ self.data = {}
58
+ end
59
+ end
@@ -1,3 +1,3 @@
1
1
  module KubesAws
2
- VERSION = "0.0.0.beta"
2
+ VERSION = "0.1.0"
3
3
  end
metadata CHANGED
@@ -1,15 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubes_aws
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.beta
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2020-10-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-eks
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk-iam
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aws-sdk-secretsmanager
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: aws-sdk-ssm
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: aws_data
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: memoist
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: zeitwerk
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
13
125
  description:
14
126
  email:
15
127
  - tung@boltops.com
@@ -19,12 +131,21 @@ extra_rdoc_files: []
19
131
  files:
20
132
  - ".gitignore"
21
133
  - ".rspec"
134
+ - CHANGELOG.md
22
135
  - Gemfile
23
136
  - LICENSE.txt
24
137
  - README.md
25
138
  - Rakefile
26
139
  - kubes_aws.gemspec
27
140
  - lib/kubes_aws.rb
141
+ - lib/kubes_aws/autoloader.rb
142
+ - lib/kubes_aws/aws_services.rb
143
+ - lib/kubes_aws/iam_role.rb
144
+ - lib/kubes_aws/iam_role/prebaked.rb
145
+ - lib/kubes_aws/logging.rb
146
+ - lib/kubes_aws/open_id.rb
147
+ - lib/kubes_aws/secrets.rb
148
+ - lib/kubes_aws/ssm.rb
28
149
  - lib/kubes_aws/version.rb
29
150
  homepage: https://github.com/boltops-tools/kubes_aws
30
151
  licenses:
@@ -42,9 +163,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
163
  version: 2.3.0
43
164
  required_rubygems_version: !ruby/object:Gem::Requirement
44
165
  requirements:
45
- - - ">"
166
+ - - ">="
46
167
  - !ruby/object:Gem::Version
47
- version: 1.3.1
168
+ version: '0'
48
169
  requirements: []
49
170
  rubygems_version: 3.1.2
50
171
  signing_key: