skull_island 2.0.5 → 2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +16 -3
- data/.travis.yml +3 -3
- data/Dockerfile +1 -1
- data/Gemfile.lock +65 -59
- data/README.md +27 -9
- data/lib/skull_island.rb +1 -1
- 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 +4 -3
- data/lib/skull_island/resource_collection.rb +4 -3
- data/lib/skull_island/resources/ca_certificate.rb +29 -3
- data/lib/skull_island/resources/certificate.rb +29 -5
- data/lib/skull_island/resources/consumer.rb +6 -3
- data/lib/skull_island/resources/jwt_credential.rb +2 -0
- data/lib/skull_island/resources/plugin.rb +20 -12
- data/lib/skull_island/resources/route.rb +4 -0
- data/lib/skull_island/resources/service.rb +70 -3
- data/lib/skull_island/resources/upstream_target.rb +4 -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 +6 -6
- metadata +20 -20
@@ -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
|
|
@@ -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)
|
@@ -32,10 +36,13 @@ module SkullIsland
|
|
32
36
|
|
33
37
|
known_ids
|
34
38
|
end
|
39
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
40
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
35
41
|
|
36
42
|
def export(options = {})
|
37
43
|
hash = { 'cert' => cert }
|
38
44
|
hash['tags'] = tags unless tags.empty?
|
45
|
+
hash['name'] = name if name
|
39
46
|
[*options[:exclude]].each do |exclude|
|
40
47
|
hash.delete(exclude.to_s)
|
41
48
|
end
|
@@ -48,10 +55,19 @@ module SkullIsland
|
|
48
55
|
def modified_existing?
|
49
56
|
return false unless new?
|
50
57
|
|
51
|
-
# Find CA certs of the same
|
52
|
-
|
58
|
+
# Find CA certs of the same "name"
|
59
|
+
if name
|
60
|
+
same_name = self.class.where(:name, name)
|
53
61
|
|
54
|
-
|
62
|
+
existing = same_name.size == 1 ? same_name.first : nil
|
63
|
+
end
|
64
|
+
|
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
|
55
71
|
|
56
72
|
if existing
|
57
73
|
@entity['id'] = existing.id
|
@@ -61,6 +77,16 @@ module SkullIsland
|
|
61
77
|
end
|
62
78
|
end
|
63
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
|
+
|
64
90
|
private
|
65
91
|
|
66
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)
|
@@ -38,13 +40,15 @@ module SkullIsland
|
|
38
40
|
|
39
41
|
known_ids
|
40
42
|
end
|
41
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
42
43
|
# rubocop:enable Metrics/PerceivedComplexity
|
44
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
43
45
|
|
46
|
+
# rubocop:disable Metrics/AbcSize
|
44
47
|
def export(options = {})
|
45
48
|
hash = { 'cert' => cert, 'key' => key }
|
46
49
|
hash['snis'] = snis if snis && !snis.empty?
|
47
50
|
hash['tags'] = tags unless tags.empty?
|
51
|
+
hash['name'] = name if name
|
48
52
|
[*options[:exclude]].each do |exclude|
|
49
53
|
hash.delete(exclude.to_s)
|
50
54
|
end
|
@@ -53,14 +57,24 @@ module SkullIsland
|
|
53
57
|
end
|
54
58
|
hash.reject { |_, value| value.nil? }
|
55
59
|
end
|
60
|
+
# rubocop:enable Metrics/AbcSize
|
56
61
|
|
57
62
|
def modified_existing?
|
58
63
|
return false unless new?
|
59
64
|
|
60
|
-
# Find certs of the same
|
61
|
-
|
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
|
71
|
+
|
72
|
+
unless existing
|
73
|
+
# Find certs of the same cert and key
|
74
|
+
same_key = self.class.where(:key, key)
|
62
75
|
|
63
|
-
|
76
|
+
existing = same_key.size == 1 ? same_key.first : nil
|
77
|
+
end
|
64
78
|
|
65
79
|
if existing
|
66
80
|
@entity['id'] = existing.id
|
@@ -70,6 +84,16 @@ module SkullIsland
|
|
70
84
|
end
|
71
85
|
end
|
72
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
|
+
|
73
97
|
private
|
74
98
|
|
75
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
|
@@ -105,9 +105,10 @@ module SkullIsland
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def add_acl!(details)
|
108
|
-
r =
|
108
|
+
r = case details
|
109
|
+
when AccessControlList
|
109
110
|
details
|
110
|
-
|
111
|
+
when String
|
111
112
|
resource = AccessControlList.new(api_client: api_client)
|
112
113
|
resource.group = details
|
113
114
|
resource
|
@@ -164,6 +165,7 @@ module SkullIsland
|
|
164
165
|
Plugin.where(:consumer, self, api_client: api_client)
|
165
166
|
end
|
166
167
|
|
168
|
+
# rubocop:disable Metrics/AbcSize
|
167
169
|
def export(options = {})
|
168
170
|
hash = { 'username' => username, 'custom_id' => custom_id }
|
169
171
|
creds = credentials_for_export
|
@@ -178,6 +180,7 @@ module SkullIsland
|
|
178
180
|
end
|
179
181
|
hash.reject { |_, value| value.nil? }
|
180
182
|
end
|
183
|
+
# rubocop:enable Metrics/AbcSize
|
181
184
|
|
182
185
|
def modified_existing?
|
183
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?
|
@@ -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
|
|
@@ -48,6 +49,7 @@ module SkullIsland
|
|
48
49
|
end
|
49
50
|
# rubocop:enable Metrics/CyclomaticComplexity
|
50
51
|
# rubocop:enable Metrics/PerceivedComplexity
|
52
|
+
# rubocop:enable Metrics/AbcSize
|
51
53
|
|
52
54
|
def self.enabled_names(api_client: APIClient.instance)
|
53
55
|
api_client.get("#{relative_uri}/enabled")['enabled_plugins']
|
@@ -123,14 +125,15 @@ module SkullIsland
|
|
123
125
|
end
|
124
126
|
|
125
127
|
def postprocess_consumer(value)
|
126
|
-
|
128
|
+
case value
|
129
|
+
when Hash
|
127
130
|
Consumer.new(
|
128
131
|
entity: value,
|
129
132
|
lazy: true,
|
130
133
|
tainted: false,
|
131
134
|
api_client: api_client
|
132
135
|
)
|
133
|
-
|
136
|
+
when String
|
134
137
|
Consumer.new(
|
135
138
|
entity: { 'id' => value },
|
136
139
|
lazy: true,
|
@@ -143,9 +146,10 @@ module SkullIsland
|
|
143
146
|
end
|
144
147
|
|
145
148
|
def preprocess_consumer(input)
|
146
|
-
|
149
|
+
case input
|
150
|
+
when Hash
|
147
151
|
input
|
148
|
-
|
152
|
+
when Consumer
|
149
153
|
{ 'id' => input.id }
|
150
154
|
else
|
151
155
|
input
|
@@ -153,14 +157,15 @@ module SkullIsland
|
|
153
157
|
end
|
154
158
|
|
155
159
|
def postprocess_route(value)
|
156
|
-
|
160
|
+
case value
|
161
|
+
when Hash
|
157
162
|
Route.new(
|
158
163
|
entity: value,
|
159
164
|
lazy: true,
|
160
165
|
tainted: false,
|
161
166
|
api_client: api_client
|
162
167
|
)
|
163
|
-
|
168
|
+
when String
|
164
169
|
Route.new(
|
165
170
|
entity: { 'id' => value },
|
166
171
|
lazy: true,
|
@@ -173,9 +178,10 @@ module SkullIsland
|
|
173
178
|
end
|
174
179
|
|
175
180
|
def preprocess_route(input)
|
176
|
-
|
181
|
+
case input
|
182
|
+
when Hash
|
177
183
|
input
|
178
|
-
|
184
|
+
when Route
|
179
185
|
{ 'id' => input.id }
|
180
186
|
else
|
181
187
|
input
|
@@ -183,14 +189,15 @@ module SkullIsland
|
|
183
189
|
end
|
184
190
|
|
185
191
|
def postprocess_service(value)
|
186
|
-
|
192
|
+
case value
|
193
|
+
when Hash
|
187
194
|
Service.new(
|
188
195
|
entity: value,
|
189
196
|
lazy: true,
|
190
197
|
tainted: false,
|
191
198
|
api_client: api_client
|
192
199
|
)
|
193
|
-
|
200
|
+
when String
|
194
201
|
Service.new(
|
195
202
|
entity: { 'id' => value },
|
196
203
|
lazy: true,
|
@@ -203,9 +210,10 @@ module SkullIsland
|
|
203
210
|
end
|
204
211
|
|
205
212
|
def preprocess_service(input)
|
206
|
-
|
213
|
+
case input
|
214
|
+
when Hash
|
207
215
|
input
|
208
|
-
|
216
|
+
when Service
|
209
217
|
{ 'id' => input.id }
|
210
218
|
else
|
211
219
|
input
|