skull_island 2.0.3 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +45 -2
- data/.travis.yml +3 -1
- data/Gemfile.lock +82 -64
- data/README.md +24 -8
- data/lib/skull_island/cli.rb +10 -6
- data/lib/skull_island/helpers/meta.rb +1 -1
- data/lib/skull_island/helpers/resource.rb +16 -7
- data/lib/skull_island/lru_cache.rb +1 -1
- data/lib/skull_island/resource.rb +8 -5
- data/lib/skull_island/resource_collection.rb +4 -3
- data/lib/skull_island/resources/ca_certificate.rb +31 -3
- data/lib/skull_island/resources/certificate.rb +31 -5
- data/lib/skull_island/resources/consumer.rb +8 -3
- data/lib/skull_island/resources/jwt_credential.rb +4 -1
- data/lib/skull_island/resources/plugin.rb +22 -12
- data/lib/skull_island/resources/route.rb +10 -2
- data/lib/skull_island/resources/service.rb +80 -6
- data/lib/skull_island/resources/upstream.rb +2 -0
- data/lib/skull_island/resources/upstream_target.rb +6 -3
- data/lib/skull_island/validations/api_client.rb +1 -1
- data/lib/skull_island/validations/resource.rb +1 -1
- data/lib/skull_island/version.rb +2 -2
- data/skull_island.gemspec +3 -3
- metadata +8 -9
@@ -44,7 +44,7 @@ module SkullIsland
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def project=(project_id)
|
47
|
-
unless project_id.is_a?(String) && project_id.match?(/^[\w_
|
47
|
+
unless project_id.is_a?(String) && project_id.match?(/^[\w_\-.~]+$/)
|
48
48
|
raise Exceptions::InvalidArguments, 'project'
|
49
49
|
end
|
50
50
|
|
@@ -16,6 +16,8 @@ module SkullIsland
|
|
16
16
|
|
17
17
|
# rubocop:disable Style/GuardClause
|
18
18
|
# rubocop:disable Security/Eval
|
19
|
+
# The delayed_set method allows a second phase of Erb templating immediately
|
20
|
+
# before sending data to the API. This allows the `lookup` function to work dynamically
|
19
21
|
def delayed_set(property, data, key = property.to_s)
|
20
22
|
if data[key]
|
21
23
|
value = recursive_erubi(data[key])
|
@@ -27,11 +29,12 @@ module SkullIsland
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def recursive_erubi(data)
|
30
|
-
|
32
|
+
case data
|
33
|
+
when String
|
31
34
|
eval(Erubi::Engine.new(data).src)
|
32
|
-
|
35
|
+
when Array
|
33
36
|
data.map { |item| recursive_erubi(item) }
|
34
|
-
|
37
|
+
when Hash
|
35
38
|
data.map { |k, v| [k, recursive_erubi(v)] }.to_h
|
36
39
|
else
|
37
40
|
data
|
@@ -72,7 +75,7 @@ module SkullIsland
|
|
72
75
|
end
|
73
76
|
|
74
77
|
def host_regex
|
75
|
-
/^((
|
78
|
+
/^((\w|\w[\w\-]*\w)\.)*(\w|\w[\w\-]*\w)$/
|
76
79
|
end
|
77
80
|
|
78
81
|
def id_property
|
@@ -87,9 +90,8 @@ module SkullIsland
|
|
87
90
|
self.class.immutable?
|
88
91
|
end
|
89
92
|
|
90
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
91
93
|
# rubocop:disable Metrics/PerceivedComplexity
|
92
|
-
def import_update_or_skip(verbose: false, test: false
|
94
|
+
def import_update_or_skip(index:, verbose: false, test: false)
|
93
95
|
if find_by_digest
|
94
96
|
puts "[INFO] Skipping #{self.class} index #{index} (#{id})" if verbose
|
95
97
|
elsif test
|
@@ -102,12 +104,16 @@ module SkullIsland
|
|
102
104
|
puts "[ERR] Failed to save #{self.class} index #{index}"
|
103
105
|
end
|
104
106
|
end
|
105
|
-
|
107
|
+
|
106
108
|
# rubocop:enable Metrics/PerceivedComplexity
|
107
109
|
|
108
110
|
# Looks up IDs (and usually wraps them in a Hash)
|
109
111
|
def lookup(type, value, raw = false)
|
110
112
|
id_value = case type
|
113
|
+
when :ca_certificate
|
114
|
+
Resources::CACertificate.find(:name, value).id
|
115
|
+
when :certificate
|
116
|
+
Resources::Certificate.find(:name, value).id
|
111
117
|
when :consumer
|
112
118
|
Resources::Consumer.find(:username, value).id
|
113
119
|
when :route
|
@@ -209,6 +215,9 @@ module SkullIsland
|
|
209
215
|
@api_client.invalidate_cache_for(self.class.relative_uri.to_s) # clear any collection class
|
210
216
|
@tainted = false
|
211
217
|
true
|
218
|
+
rescue RestClient::BadRequest => e
|
219
|
+
warn "[WARN] Failed to save #{self.class} via #{new? ? save_uri : relative_uri} with " \
|
220
|
+
"'#{e.message}':\n#{saveable_data.to_yaml}\n\nReceived: #{e.inspect}"
|
212
221
|
end
|
213
222
|
|
214
223
|
def save_uri
|
@@ -11,7 +11,7 @@ module SkullIsland
|
|
11
11
|
attr_reader :max_size, :keys
|
12
12
|
|
13
13
|
# @raise [Exceptions::InvalidCacheSize] if the max_size isn't an Integer
|
14
|
-
def initialize(max_size =
|
14
|
+
def initialize(max_size = 10_000)
|
15
15
|
raise Exceptions::InvalidCacheSize unless max_size.is_a?(Integer)
|
16
16
|
|
17
17
|
@max_size = max_size
|
@@ -57,9 +57,10 @@ module SkullIsland
|
|
57
57
|
define_method(method_name) do |value|
|
58
58
|
raise Exceptions::ImmutableModification if immutable?
|
59
59
|
|
60
|
-
if opts[:validate]
|
61
|
-
raise Exceptions::InvalidArguments, name
|
60
|
+
if opts[:validate] && !send("validate_#{name}".to_sym, value)
|
61
|
+
raise Exceptions::InvalidArguments, name
|
62
62
|
end
|
63
|
+
|
63
64
|
@entity[name.to_s] = if opts[:preprocess]
|
64
65
|
send("preprocess_#{name}".to_sym, value)
|
65
66
|
else
|
@@ -121,7 +122,7 @@ module SkullIsland
|
|
121
122
|
)
|
122
123
|
end
|
123
124
|
|
124
|
-
def self.from_hash(hash)
|
125
|
+
def self.from_hash(hash, options = {})
|
125
126
|
# TODO: better options validations
|
126
127
|
raise Exceptions::InvalidOptions unless options.is_a?(Hash)
|
127
128
|
|
@@ -179,8 +180,10 @@ module SkullIsland
|
|
179
180
|
)
|
180
181
|
end
|
181
182
|
|
182
|
-
def self.cleanup_except(project, keep_these)
|
183
|
-
where(:project, project)
|
183
|
+
def self.cleanup_except(project, keep_these, from_these = nil)
|
184
|
+
old_resources = from_these || where(:project, project)
|
185
|
+
|
186
|
+
old_resources.reject { |res| keep_these.include?(res.id) }.map do |res|
|
184
187
|
puts "[WARN] ! Removing #{name} (#{res.id})"
|
185
188
|
res.destroy
|
186
189
|
end
|
@@ -161,9 +161,10 @@ module SkullIsland
|
|
161
161
|
# use #merge
|
162
162
|
# @return [ResourceCollection]
|
163
163
|
def +(other)
|
164
|
-
|
164
|
+
case other
|
165
|
+
when self.class
|
165
166
|
self.class.new(@list + other.to_a, type: @type, api_client: @api_client)
|
166
|
-
|
167
|
+
when @type
|
167
168
|
self.class.new(@list + [other], type: @type, api_client: @api_client)
|
168
169
|
else
|
169
170
|
raise Exceptions::InvalidArguments
|
@@ -171,7 +172,7 @@ module SkullIsland
|
|
171
172
|
end
|
172
173
|
|
173
174
|
def <<(other)
|
174
|
-
raise Exceptions::InvalidArguments, 'Resource Type Mismatch' unless other.
|
175
|
+
raise Exceptions::InvalidArguments, 'Resource Type Mismatch' unless other.instance_of?(@type)
|
175
176
|
|
176
177
|
@list << other
|
177
178
|
end
|
@@ -12,7 +12,10 @@ module SkullIsland
|
|
12
12
|
property :cert, required: true, validate: true
|
13
13
|
property :created_at, read_only: true, postprocess: true
|
14
14
|
property :tags, validate: true, preprocess: true, postprocess: true
|
15
|
+
# property :name
|
15
16
|
|
17
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
18
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
16
19
|
def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
|
17
20
|
raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
|
18
21
|
|
@@ -22,6 +25,7 @@ module SkullIsland
|
|
22
25
|
resource = new
|
23
26
|
resource.delayed_set(:cert, resource_data)
|
24
27
|
resource.tags = resource_data['tags'] if resource_data['tags']
|
28
|
+
resource.name = resource_data['name'] if resource_data['name']
|
25
29
|
resource.project = project if project
|
26
30
|
resource.import_time = (time || Time.now.utc.to_i) if project
|
27
31
|
resource.import_update_or_skip(index: index, verbose: verbose, test: test)
|
@@ -29,11 +33,16 @@ module SkullIsland
|
|
29
33
|
end
|
30
34
|
|
31
35
|
cleanup_except(project, known_ids) if project
|
36
|
+
|
37
|
+
known_ids
|
32
38
|
end
|
39
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
40
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
33
41
|
|
34
42
|
def export(options = {})
|
35
43
|
hash = { 'cert' => cert }
|
36
44
|
hash['tags'] = tags unless tags.empty?
|
45
|
+
hash['name'] = name if name
|
37
46
|
[*options[:exclude]].each do |exclude|
|
38
47
|
hash.delete(exclude.to_s)
|
39
48
|
end
|
@@ -46,10 +55,19 @@ module SkullIsland
|
|
46
55
|
def modified_existing?
|
47
56
|
return false unless new?
|
48
57
|
|
49
|
-
# Find CA certs of the same
|
50
|
-
|
58
|
+
# Find CA certs of the same "name"
|
59
|
+
if name
|
60
|
+
same_name = self.class.where(:name, name)
|
61
|
+
|
62
|
+
existing = same_name.size == 1 ? same_name.first : nil
|
63
|
+
end
|
51
64
|
|
52
|
-
existing
|
65
|
+
unless existing
|
66
|
+
# Find CA certs of the same cert
|
67
|
+
same_cert = self.class.where(:cert, cert)
|
68
|
+
|
69
|
+
existing = same_cert.size == 1 ? same_cert.first : nil
|
70
|
+
end
|
53
71
|
|
54
72
|
if existing
|
55
73
|
@entity['id'] = existing.id
|
@@ -59,6 +77,16 @@ module SkullIsland
|
|
59
77
|
end
|
60
78
|
end
|
61
79
|
|
80
|
+
# Simulates retrieving a #name property via a tag
|
81
|
+
def name
|
82
|
+
metatags['name']
|
83
|
+
end
|
84
|
+
|
85
|
+
# Simulates setting a #name property via a tag
|
86
|
+
def name=(value)
|
87
|
+
add_meta('name', value.to_s)
|
88
|
+
end
|
89
|
+
|
62
90
|
private
|
63
91
|
|
64
92
|
# Used to validate {#cert} on set
|
@@ -12,11 +12,12 @@ module SkullIsland
|
|
12
12
|
property :cert, required: true, validate: true
|
13
13
|
property :key, required: true, validate: true
|
14
14
|
property :snis, validate: true
|
15
|
+
# property :name
|
15
16
|
property :created_at, read_only: true, postprocess: true
|
16
17
|
property :tags, validate: true, preprocess: true, postprocess: true
|
17
18
|
|
18
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
19
19
|
# rubocop:disable Metrics/PerceivedComplexity
|
20
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
20
21
|
def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
|
21
22
|
raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
|
22
23
|
|
@@ -28,6 +29,7 @@ module SkullIsland
|
|
28
29
|
resource.delayed_set(:key, resource_data)
|
29
30
|
resource.snis = resource_data['snis'] if resource_data['snis']
|
30
31
|
resource.tags = resource_data['tags'] if resource_data['tags']
|
32
|
+
resource.name = resource_data['name'] if resource_data['name']
|
31
33
|
resource.project = project if project
|
32
34
|
resource.import_time = (time || Time.now.utc.to_i) if project
|
33
35
|
resource.import_update_or_skip(index: index, verbose: verbose, test: test)
|
@@ -35,14 +37,18 @@ module SkullIsland
|
|
35
37
|
end
|
36
38
|
|
37
39
|
cleanup_except(project, known_ids) if project
|
40
|
+
|
41
|
+
known_ids
|
38
42
|
end
|
39
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
40
43
|
# rubocop:enable Metrics/PerceivedComplexity
|
44
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
41
45
|
|
46
|
+
# rubocop:disable Metrics/AbcSize
|
42
47
|
def export(options = {})
|
43
48
|
hash = { 'cert' => cert, 'key' => key }
|
44
49
|
hash['snis'] = snis if snis && !snis.empty?
|
45
50
|
hash['tags'] = tags unless tags.empty?
|
51
|
+
hash['name'] = name if name
|
46
52
|
[*options[:exclude]].each do |exclude|
|
47
53
|
hash.delete(exclude.to_s)
|
48
54
|
end
|
@@ -51,14 +57,24 @@ module SkullIsland
|
|
51
57
|
end
|
52
58
|
hash.reject { |_, value| value.nil? }
|
53
59
|
end
|
60
|
+
# rubocop:enable Metrics/AbcSize
|
54
61
|
|
55
62
|
def modified_existing?
|
56
63
|
return false unless new?
|
57
64
|
|
58
|
-
# Find certs of the same
|
59
|
-
|
65
|
+
# Find certs of the same "name"
|
66
|
+
if name
|
67
|
+
same_name = self.class.where(:name, name)
|
68
|
+
|
69
|
+
existing = same_name.size == 1 ? same_name.first : nil
|
70
|
+
end
|
60
71
|
|
61
|
-
existing
|
72
|
+
unless existing
|
73
|
+
# Find certs of the same cert and key
|
74
|
+
same_key = self.class.where(:key, key)
|
75
|
+
|
76
|
+
existing = same_key.size == 1 ? same_key.first : nil
|
77
|
+
end
|
62
78
|
|
63
79
|
if existing
|
64
80
|
@entity['id'] = existing.id
|
@@ -68,6 +84,16 @@ module SkullIsland
|
|
68
84
|
end
|
69
85
|
end
|
70
86
|
|
87
|
+
# Simulates retrieving a #name property via a tag
|
88
|
+
def name
|
89
|
+
metatags['name']
|
90
|
+
end
|
91
|
+
|
92
|
+
# Simulates setting a #name property via a tag
|
93
|
+
def name=(value)
|
94
|
+
add_meta('name', value.to_s)
|
95
|
+
end
|
96
|
+
|
71
97
|
private
|
72
98
|
|
73
99
|
# Used to validate {#cert} on set
|
@@ -60,7 +60,7 @@ module SkullIsland
|
|
60
60
|
|
61
61
|
known_acls = AccessControlList.batch_import(
|
62
62
|
(
|
63
|
-
resource_data
|
63
|
+
resource_data['acls'] || []
|
64
64
|
).map { |t| t.merge('consumer' => { 'id' => resource.id }) },
|
65
65
|
verbose: verbose,
|
66
66
|
test: test
|
@@ -95,6 +95,8 @@ module SkullIsland
|
|
95
95
|
# rubocop:enable Metrics/BlockLength
|
96
96
|
|
97
97
|
cleanup_except(project, known_ids) if project
|
98
|
+
|
99
|
+
known_ids
|
98
100
|
end
|
99
101
|
# rubocop:enable Metrics/MethodLength
|
100
102
|
|
@@ -103,9 +105,10 @@ module SkullIsland
|
|
103
105
|
end
|
104
106
|
|
105
107
|
def add_acl!(details)
|
106
|
-
r =
|
108
|
+
r = case details
|
109
|
+
when AccessControlList
|
107
110
|
details
|
108
|
-
|
111
|
+
when String
|
109
112
|
resource = AccessControlList.new(api_client: api_client)
|
110
113
|
resource.group = details
|
111
114
|
resource
|
@@ -162,6 +165,7 @@ module SkullIsland
|
|
162
165
|
Plugin.where(:consumer, self, api_client: api_client)
|
163
166
|
end
|
164
167
|
|
168
|
+
# rubocop:disable Metrics/AbcSize
|
165
169
|
def export(options = {})
|
166
170
|
hash = { 'username' => username, 'custom_id' => custom_id }
|
167
171
|
creds = credentials_for_export
|
@@ -176,6 +180,7 @@ module SkullIsland
|
|
176
180
|
end
|
177
181
|
hash.reject { |_, value| value.nil? }
|
178
182
|
end
|
183
|
+
# rubocop:enable Metrics/AbcSize
|
179
184
|
|
180
185
|
def modified_existing?
|
181
186
|
return false unless new?
|
@@ -48,6 +48,7 @@ module SkullIsland
|
|
48
48
|
consumer ? "#{consumer.relative_uri}/jwt" : nil
|
49
49
|
end
|
50
50
|
|
51
|
+
# rubocop:disable Metrics/AbcSize
|
51
52
|
def export(options = {})
|
52
53
|
hash = { 'algorithm' => algorithm }
|
53
54
|
hash['key'] = key if key
|
@@ -62,6 +63,7 @@ module SkullIsland
|
|
62
63
|
end
|
63
64
|
hash.reject { |_, value| value.nil? }
|
64
65
|
end
|
66
|
+
# rubocop:enable Metrics/AbcSize
|
65
67
|
|
66
68
|
# Keys can't be updated, only created or deleted
|
67
69
|
def modified_existing?
|
@@ -102,9 +104,10 @@ module SkullIsland
|
|
102
104
|
end
|
103
105
|
|
104
106
|
# Used to validate {#algorithm} on set
|
107
|
+
# @see https://github.com/Kong/kong/blob/master/kong/plugins/jwt/daos.lua#L29
|
105
108
|
def validate_algorithm(value)
|
106
109
|
# allow a String
|
107
|
-
%w[HS256 HS384 HS512 RS256 ES256].include? value
|
110
|
+
%w[HS256 HS384 HS512 RS256 RS512 ES256].include? value
|
108
111
|
end
|
109
112
|
|
110
113
|
# Used to validate {#key} on set
|
@@ -21,6 +21,7 @@ module SkullIsland
|
|
21
21
|
|
22
22
|
# rubocop:disable Metrics/CyclomaticComplexity
|
23
23
|
# rubocop:disable Metrics/PerceivedComplexity
|
24
|
+
# rubocop:disable Metrics/AbcSize
|
24
25
|
def self.batch_import(data, verbose: false, test: false, project: nil, time: nil)
|
25
26
|
raise(Exceptions::InvalidArguments) unless data.is_a?(Array)
|
26
27
|
|
@@ -43,9 +44,12 @@ module SkullIsland
|
|
43
44
|
end
|
44
45
|
|
45
46
|
cleanup_except(project, known_ids) if project
|
47
|
+
|
48
|
+
known_ids
|
46
49
|
end
|
47
50
|
# rubocop:enable Metrics/CyclomaticComplexity
|
48
51
|
# rubocop:enable Metrics/PerceivedComplexity
|
52
|
+
# rubocop:enable Metrics/AbcSize
|
49
53
|
|
50
54
|
def self.enabled_names(api_client: APIClient.instance)
|
51
55
|
api_client.get("#{relative_uri}/enabled")['enabled_plugins']
|
@@ -121,14 +125,15 @@ module SkullIsland
|
|
121
125
|
end
|
122
126
|
|
123
127
|
def postprocess_consumer(value)
|
124
|
-
|
128
|
+
case value
|
129
|
+
when Hash
|
125
130
|
Consumer.new(
|
126
131
|
entity: value,
|
127
132
|
lazy: true,
|
128
133
|
tainted: false,
|
129
134
|
api_client: api_client
|
130
135
|
)
|
131
|
-
|
136
|
+
when String
|
132
137
|
Consumer.new(
|
133
138
|
entity: { 'id' => value },
|
134
139
|
lazy: true,
|
@@ -141,9 +146,10 @@ module SkullIsland
|
|
141
146
|
end
|
142
147
|
|
143
148
|
def preprocess_consumer(input)
|
144
|
-
|
149
|
+
case input
|
150
|
+
when Hash
|
145
151
|
input
|
146
|
-
|
152
|
+
when Consumer
|
147
153
|
{ 'id' => input.id }
|
148
154
|
else
|
149
155
|
input
|
@@ -151,14 +157,15 @@ module SkullIsland
|
|
151
157
|
end
|
152
158
|
|
153
159
|
def postprocess_route(value)
|
154
|
-
|
160
|
+
case value
|
161
|
+
when Hash
|
155
162
|
Route.new(
|
156
163
|
entity: value,
|
157
164
|
lazy: true,
|
158
165
|
tainted: false,
|
159
166
|
api_client: api_client
|
160
167
|
)
|
161
|
-
|
168
|
+
when String
|
162
169
|
Route.new(
|
163
170
|
entity: { 'id' => value },
|
164
171
|
lazy: true,
|
@@ -171,9 +178,10 @@ module SkullIsland
|
|
171
178
|
end
|
172
179
|
|
173
180
|
def preprocess_route(input)
|
174
|
-
|
181
|
+
case input
|
182
|
+
when Hash
|
175
183
|
input
|
176
|
-
|
184
|
+
when Route
|
177
185
|
{ 'id' => input.id }
|
178
186
|
else
|
179
187
|
input
|
@@ -181,14 +189,15 @@ module SkullIsland
|
|
181
189
|
end
|
182
190
|
|
183
191
|
def postprocess_service(value)
|
184
|
-
|
192
|
+
case value
|
193
|
+
when Hash
|
185
194
|
Service.new(
|
186
195
|
entity: value,
|
187
196
|
lazy: true,
|
188
197
|
tainted: false,
|
189
198
|
api_client: api_client
|
190
199
|
)
|
191
|
-
|
200
|
+
when String
|
192
201
|
Service.new(
|
193
202
|
entity: { 'id' => value },
|
194
203
|
lazy: true,
|
@@ -201,9 +210,10 @@ module SkullIsland
|
|
201
210
|
end
|
202
211
|
|
203
212
|
def preprocess_service(input)
|
204
|
-
|
213
|
+
case input
|
214
|
+
when Hash
|
205
215
|
input
|
206
|
-
|
216
|
+
when Service
|
207
217
|
{ 'id' => input.id }
|
208
218
|
else
|
209
219
|
input
|