jets 2.1.7 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/jets.gemspec +1 -0
- data/lib/jets.rb +2 -1
- data/lib/jets/application/defaults.rb +3 -0
- data/lib/jets/authorizer/base.rb +36 -0
- data/lib/jets/authorizer/dsl.rb +64 -0
- data/lib/jets/authorizer/helpers/iam_helper.rb +50 -0
- data/lib/jets/camelizer.rb +3 -70
- data/lib/jets/cfn/builders/api_deployment_builder.rb +0 -2
- data/lib/jets/cfn/builders/api_gateway_builder.rb +0 -2
- data/lib/jets/cfn/builders/api_resources_builder.rb +0 -2
- data/lib/jets/cfn/builders/authorizer_builder.rb +67 -0
- data/lib/jets/cfn/builders/base_child_builder.rb +2 -2
- data/lib/jets/cfn/builders/controller_builder.rb +4 -1
- data/lib/jets/cfn/builders/interface.rb +6 -4
- data/lib/jets/cfn/builders/parent_builder.rb +22 -10
- data/lib/jets/commands/build.rb +126 -103
- data/lib/jets/controller/authorization.rb +72 -0
- data/lib/jets/controller/base.rb +25 -43
- data/lib/jets/controller/middleware/cors.rb +1 -1
- data/lib/jets/klass.rb +3 -3
- data/lib/jets/naming.rb +12 -2
- data/lib/jets/resource/api_gateway/authorizer.rb +82 -0
- data/lib/jets/resource/api_gateway/method.rb +23 -38
- data/lib/jets/resource/api_gateway/method/authorization.rb +32 -0
- data/lib/jets/resource/child_stack/app_class.rb +16 -5
- data/lib/jets/resource/child_stack/authorizer.rb +46 -0
- data/lib/jets/resource/child_stack/common_parameters.rb +14 -0
- data/lib/jets/resource/child_stack/shared.rb +2 -9
- data/lib/jets/router/route.rb +1 -8
- data/lib/jets/router/route/authorization.rb +48 -0
- data/lib/jets/rule/dsl.rb +0 -1
- data/lib/jets/version.rb +1 -1
- metadata +26 -2
@@ -52,7 +52,7 @@ module Jets::Controller::Middleware
|
|
52
52
|
# IE: Access-Control-Allow-Methods
|
53
53
|
default = {
|
54
54
|
"access-control-allow-methods" => "DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT",
|
55
|
-
"access-control-allow-headers" => "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent",
|
55
|
+
"access-control-allow-headers" => "Content-Type,X-Amz-Date,Authorization,Auth,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent",
|
56
56
|
}
|
57
57
|
Jets.config.cors_preflight || default
|
58
58
|
end
|
data/lib/jets/klass.rb
CHANGED
@@ -45,15 +45,15 @@ class Jets::Klass
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
APP_TYPES = %w[controller job rule authorizer]
|
48
49
|
def from_task(task)
|
49
50
|
class_name = task.class_name
|
50
51
|
filename = class_name.underscore
|
51
52
|
|
52
|
-
# Examples of filename: posts_controller, hard_job, security_rule,
|
53
|
+
# Examples of filename: posts_controller, hard_job, security_rule, main_authorizer
|
53
54
|
# hello_function, hello
|
54
|
-
valid_types = %w[controller job rule]
|
55
55
|
type = filename.split('_').last
|
56
|
-
type = "function" unless
|
56
|
+
type = "function" unless APP_TYPES.include?(type)
|
57
57
|
|
58
58
|
path = "app/#{type.pluralize}/#{filename}.rb"
|
59
59
|
from_path(path)
|
data/lib/jets/naming.rb
CHANGED
@@ -7,12 +7,12 @@ class Jets::Naming
|
|
7
7
|
extend Memoist
|
8
8
|
|
9
9
|
def app_template_path(app_class)
|
10
|
-
underscored =
|
10
|
+
underscored = underscore(app_class)
|
11
11
|
"#{template_path_prefix}-app-#{underscored}.yml"
|
12
12
|
end
|
13
13
|
|
14
14
|
def shared_template_path(shared_class)
|
15
|
-
underscored =
|
15
|
+
underscored = underscore(shared_class)
|
16
16
|
"#{template_path_prefix}-shared-#{underscored}.yml"
|
17
17
|
end
|
18
18
|
|
@@ -49,5 +49,15 @@ class Jets::Naming
|
|
49
49
|
def gateway_api_name
|
50
50
|
"#{Jets.config.project_namespace}"
|
51
51
|
end
|
52
|
+
|
53
|
+
def authorizer_template_path(path)
|
54
|
+
underscored = underscore(path)
|
55
|
+
underscored.sub!(/^app-/, '')
|
56
|
+
"#{template_path_prefix}-#{underscored}.yml"
|
57
|
+
end
|
58
|
+
|
59
|
+
def underscore(s)
|
60
|
+
s.to_s.underscore.sub(/\.rb$/,'').gsub('/','-')
|
61
|
+
end
|
52
62
|
end
|
53
63
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Jets::Resource::ApiGateway
|
2
|
+
class Authorizer < Jets::Resource::Base
|
3
|
+
def initialize(props={})
|
4
|
+
@props = props # associated_properties from dsl.rb
|
5
|
+
end
|
6
|
+
|
7
|
+
def definition
|
8
|
+
{
|
9
|
+
authorizer_logical_id => {
|
10
|
+
type: "AWS::ApiGateway::Authorizer",
|
11
|
+
properties: props,
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def props
|
17
|
+
default = {
|
18
|
+
# authorizer_credentials: '',
|
19
|
+
# authorizer_result_ttl_in_seconds: '',
|
20
|
+
# auth_type: '',
|
21
|
+
# identity_source: '', # required
|
22
|
+
# identity_validation_expression: '',
|
23
|
+
# name: '',
|
24
|
+
# provider_arns: [],
|
25
|
+
rest_api_id: '!Ref RestApi', # Required: Yes
|
26
|
+
type: '', # Required: Yes
|
27
|
+
}
|
28
|
+
|
29
|
+
unless @props[:type].to_s.upcase == 'COGNITO_USER_POOLS'
|
30
|
+
@props[:authorizer_uri] = { # Required: Conditional
|
31
|
+
"Fn::Join" => ['', [
|
32
|
+
'arn:aws:apigateway:',
|
33
|
+
"!Ref 'AWS::Region'",
|
34
|
+
':lambda:path/2015-03-31/functions/',
|
35
|
+
{"Fn::GetAtt" => ["{namespace}LambdaFunction", "Arn"]},
|
36
|
+
'/invocations'
|
37
|
+
]]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
@props[:authorizer_result_ttl_in_seconds] = @props.delete(:ttl) if @props[:ttl]
|
41
|
+
|
42
|
+
normalize_type!(@props)
|
43
|
+
normalize_identity_source!(@props)
|
44
|
+
default.merge(@props)
|
45
|
+
end
|
46
|
+
|
47
|
+
def authorizer_logical_id
|
48
|
+
"{namespace}_authorizer" # IE: protect_authorizer
|
49
|
+
end
|
50
|
+
|
51
|
+
def outputs
|
52
|
+
# IE: ProtectAuthorizer: !Ref ProtectAuthorizer
|
53
|
+
{
|
54
|
+
logical_id => "!Ref #{logical_id}",
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
# Also sets a default if it's not provided
|
60
|
+
def normalize_type!(props)
|
61
|
+
type = props[:type] || :request
|
62
|
+
@props[:type] = type.to_s.upcase
|
63
|
+
end
|
64
|
+
|
65
|
+
# Also sets a default if it's not provided
|
66
|
+
def normalize_identity_source!(props)
|
67
|
+
identity_source = props[:identity_source] || Jets.config.api.authorizers.default_token_source
|
68
|
+
# request authorizer type can have multiple identity sources.
|
69
|
+
# token authorizer type has only one identity source.
|
70
|
+
# We handle both cases.
|
71
|
+
identity_sources = identity_source.split(',') # to handle multipe
|
72
|
+
identity_sources.map! do |source|
|
73
|
+
if source.include?(".") # if '.' is detected assume full identify source provided
|
74
|
+
source
|
75
|
+
else
|
76
|
+
"method.request.header.#{source}" # convention
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@props[:identity_source] = identity_sources.join(',')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# Converts a Jets::Route to a CloudFormation Jets::Resource::ApiGateway::Method resource
|
2
2
|
module Jets::Resource::ApiGateway
|
3
3
|
class Method < Jets::Resource::Base
|
4
|
+
include Authorization
|
5
|
+
|
4
6
|
# also delegate permission for a method
|
5
|
-
delegate :permission,
|
6
|
-
to: :resource
|
7
|
+
delegate :permission, to: :resource
|
7
8
|
|
8
9
|
# route - Jets::Route
|
9
10
|
def initialize(route)
|
@@ -14,24 +15,30 @@ module Jets::Resource::ApiGateway
|
|
14
15
|
{
|
15
16
|
method_logical_id => {
|
16
17
|
type: "AWS::ApiGateway::Method",
|
17
|
-
properties:
|
18
|
-
resource_id: "!Ref #{resource_id}",
|
19
|
-
rest_api_id: "!Ref #{RestApi.logical_id}",
|
20
|
-
http_method: @route.method,
|
21
|
-
request_parameters: {},
|
22
|
-
authorization_type: authorization_type,
|
23
|
-
api_key_required: api_key_required?,
|
24
|
-
integration: {
|
25
|
-
integration_http_method: "POST",
|
26
|
-
type: "AWS_PROXY",
|
27
|
-
uri: "!Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${{namespace}LambdaFunction.Arn}/invocations"
|
28
|
-
},
|
29
|
-
method_responses: []
|
30
|
-
}
|
18
|
+
properties: props
|
31
19
|
}
|
32
20
|
}
|
33
21
|
end
|
34
22
|
|
23
|
+
def props
|
24
|
+
props = {
|
25
|
+
resource_id: "!Ref #{resource_id}",
|
26
|
+
rest_api_id: "!Ref #{RestApi.logical_id}",
|
27
|
+
http_method: @route.method,
|
28
|
+
request_parameters: {},
|
29
|
+
authorization_type: authorization_type,
|
30
|
+
api_key_required: api_key_required?,
|
31
|
+
integration: {
|
32
|
+
integration_http_method: "POST",
|
33
|
+
type: "AWS_PROXY",
|
34
|
+
uri: "!Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${{namespace}LambdaFunction.Arn}/invocations"
|
35
|
+
},
|
36
|
+
method_responses: []
|
37
|
+
}
|
38
|
+
props[:authorizer_id] = authorizer_id if authorizer_id
|
39
|
+
props
|
40
|
+
end
|
41
|
+
|
35
42
|
def method_logical_id
|
36
43
|
# https://stackoverflow.com/questions/6104240/how-do-i-strip-non-alphanumeric-characters-from-a-string-and-keep-spaces
|
37
44
|
# Add path to the logical id to allow 2 different paths to be connected to the same controller action.
|
@@ -65,28 +72,6 @@ module Jets::Resource::ApiGateway
|
|
65
72
|
|
66
73
|
private
|
67
74
|
|
68
|
-
def authorization_type
|
69
|
-
type = @route.authorization_type ||
|
70
|
-
controller_auth_type ||
|
71
|
-
Jets.config.api.authorization_type
|
72
|
-
type.to_s.upcase
|
73
|
-
end
|
74
|
-
|
75
|
-
def controller_auth_type
|
76
|
-
# Already handles inheritance via class_attribute
|
77
|
-
controller_klass.authorization_type
|
78
|
-
end
|
79
|
-
|
80
|
-
def api_key_required?
|
81
|
-
api_key_required == true
|
82
|
-
end
|
83
|
-
|
84
|
-
def api_key_required
|
85
|
-
@route.api_key_required ||
|
86
|
-
controller_klass.api_key_required ||
|
87
|
-
Jets.config.api.api_key_required
|
88
|
-
end
|
89
|
-
|
90
75
|
def controller_klass
|
91
76
|
@controller_klass ||= "#{controller_name}_controller".camelize.constantize
|
92
77
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Jets::Resource::ApiGateway::Method
|
2
|
+
module Authorization
|
3
|
+
private
|
4
|
+
def authorizer_id
|
5
|
+
if @route.authorizer
|
6
|
+
logical_id = @route.authorizer_id
|
7
|
+
elsif controller_klass.authorizer
|
8
|
+
logical_id = controller_klass.authorizer_logical_id(@route.action_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
"!Ref #{logical_id}" if logical_id
|
12
|
+
end
|
13
|
+
|
14
|
+
def authorization_type
|
15
|
+
type = @route.authorization_type ||
|
16
|
+
controller_klass.authorization_type || # Already handles inheritance via class_attribute, applies controller-wide
|
17
|
+
controller_klass.infer_authorization_type_for(@route.action_name) || # Applies specifically to route
|
18
|
+
Jets.config.api.authorization_type
|
19
|
+
type.to_s.upcase
|
20
|
+
end
|
21
|
+
|
22
|
+
def api_key_required?
|
23
|
+
api_key_required == true
|
24
|
+
end
|
25
|
+
|
26
|
+
def api_key_required
|
27
|
+
@route.api_key_required ||
|
28
|
+
controller_klass.api_key_required ||
|
29
|
+
Jets.config.api.api_key_required
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -5,6 +5,8 @@
|
|
5
5
|
#
|
6
6
|
module Jets::Resource::ChildStack
|
7
7
|
class AppClass < Base
|
8
|
+
include CommonParameters
|
9
|
+
|
8
10
|
def initialize(s3_bucket, options={})
|
9
11
|
super
|
10
12
|
@path = options[:path]
|
@@ -52,10 +54,10 @@ module Jets::Resource::ChildStack
|
|
52
54
|
end
|
53
55
|
|
54
56
|
def parameters
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
57
|
+
params = self.class.common_parameters
|
58
|
+
params.merge!(controller_params) if controller?
|
59
|
+
params.merge!(depends.params) if depends
|
60
|
+
params
|
59
61
|
end
|
60
62
|
|
61
63
|
def self.common_parameters
|
@@ -76,10 +78,13 @@ module Jets::Resource::ChildStack
|
|
76
78
|
|
77
79
|
template_path = @path
|
78
80
|
template = Jets::Cfn::BuiltTemplate.get(template_path)
|
79
|
-
template['Parameters'].
|
81
|
+
template['Parameters'].each do |p,data|
|
80
82
|
case p
|
81
83
|
when /Resource$/ # AWS::ApiGateway::Resource in api-resources templates. IE: demo-dev-api-resources-2.yml
|
82
84
|
params[p] = "!GetAtt #{api_resource_page(p)}.Outputs.#{p}"
|
85
|
+
when /Authorizer$/ # AWS::ApiGateway::Authorizer in authorizers templates. IE: demo-dev-authorizers.yml
|
86
|
+
# Description contains metadata to get the Authorizer logical id
|
87
|
+
params[p] = "!GetAtt #{authorizer_output(data["Description"])}"
|
83
88
|
when 'RootResourceId'
|
84
89
|
params[p] = "!GetAtt ApiGateway.Outputs.RootResourceId"
|
85
90
|
end
|
@@ -91,6 +96,12 @@ module Jets::Resource::ChildStack
|
|
91
96
|
ApiResource::Page.logical_id(parameter)
|
92
97
|
end
|
93
98
|
|
99
|
+
def authorizer_output(desc)
|
100
|
+
authorizer_stack, authorizer_logical_id = desc.split('.')
|
101
|
+
# IE: MainAuthorizer.Outputs.ProtectAuthorizer
|
102
|
+
"#{authorizer_stack}.Outputs.#{authorizer_logical_id}"
|
103
|
+
end
|
104
|
+
|
94
105
|
def controller?
|
95
106
|
@path.include?('_controller.yml')
|
96
107
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Jets::Resource::ChildStack
|
2
|
+
class Authorizer < Base
|
3
|
+
include CommonParameters
|
4
|
+
|
5
|
+
def initialize(s3_bucket, options={})
|
6
|
+
super
|
7
|
+
@path = options[:path]
|
8
|
+
end
|
9
|
+
|
10
|
+
def definition
|
11
|
+
logical_id = authorizer_logical_id
|
12
|
+
{
|
13
|
+
logical_id => {
|
14
|
+
type: "AWS::CloudFormation::Stack",
|
15
|
+
properties: {
|
16
|
+
template_url: template_url,
|
17
|
+
parameters: parameters,
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def parameters
|
24
|
+
params = common_parameters
|
25
|
+
params[:RestApi] = "!GetAtt ApiGateway.Outputs.RestApi"
|
26
|
+
params
|
27
|
+
end
|
28
|
+
|
29
|
+
def outputs
|
30
|
+
{
|
31
|
+
logical_id => "!Ref #{logical_id}",
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
# map the path to a camelized logical_id. IE: ProtectAuthorizer
|
36
|
+
def authorizer_logical_id
|
37
|
+
regexp = Regexp.new(".*#{Jets.config.project_namespace}-authorizers-")
|
38
|
+
authorizer_name = @path.sub(regexp, '').sub('.yml', '')
|
39
|
+
authorizer_name.underscore.camelize
|
40
|
+
end
|
41
|
+
|
42
|
+
def template_filename
|
43
|
+
@path
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Jets::Resource::ChildStack
|
2
|
+
module CommonParameters
|
3
|
+
def common_parameters
|
4
|
+
parameters = {
|
5
|
+
IamRole: "!GetAtt IamRole.Arn",
|
6
|
+
S3Bucket: "!Ref S3Bucket",
|
7
|
+
}
|
8
|
+
parameters[:GemLayer] = "!Ref GemLayer" unless Jets.poly_only?
|
9
|
+
parameters
|
10
|
+
end
|
11
|
+
|
12
|
+
extend self
|
13
|
+
end
|
14
|
+
end
|
@@ -5,6 +5,8 @@
|
|
5
5
|
#
|
6
6
|
module Jets::Resource::ChildStack
|
7
7
|
class Shared < AppClass
|
8
|
+
include CommonParameters
|
9
|
+
|
8
10
|
def initialize(s3_bucket, options={})
|
9
11
|
super
|
10
12
|
@path = options[:path]
|
@@ -39,15 +41,6 @@ module Jets::Resource::ChildStack
|
|
39
41
|
props
|
40
42
|
end
|
41
43
|
|
42
|
-
def common_parameters
|
43
|
-
parameters = {
|
44
|
-
IamRole: "!GetAtt IamRole.Arn",
|
45
|
-
S3Bucket: "!Ref S3Bucket",
|
46
|
-
}
|
47
|
-
parameters[:GemLayer] = "!Ref GemLayer" unless Jets.poly_only?
|
48
|
-
parameters
|
49
|
-
end
|
50
|
-
|
51
44
|
# Returns output keys associated with the stack. They are the resource logical ids.
|
52
45
|
def dependency_outputs(dependency)
|
53
46
|
dependency.to_s.camelize.constantize.output_keys
|
data/lib/jets/router/route.rb
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
class Jets::Router
|
7
7
|
class Route
|
8
8
|
include Util
|
9
|
+
include Authorization
|
9
10
|
|
10
11
|
CAPTURE_REGEX = "([^/]*)" # as string
|
11
12
|
|
@@ -184,14 +185,6 @@ class Jets::Router
|
|
184
185
|
end.to_h
|
185
186
|
end
|
186
187
|
|
187
|
-
def authorization_type
|
188
|
-
@options[:authorization_type]
|
189
|
-
end
|
190
|
-
|
191
|
-
def api_key_required
|
192
|
-
@options[:api_key_required]
|
193
|
-
end
|
194
|
-
|
195
188
|
private
|
196
189
|
def ensure_jets_format(path)
|
197
190
|
path.split('/').map do |s|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Jets::Router::Route
|
2
|
+
module Authorization
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
# Use by both Route and Controller
|
7
|
+
def authorizer_logical_id(authorizer, prefix_class: true)
|
8
|
+
klass, meth = authorizer.split("#")
|
9
|
+
words = [meth, "authorizer"]
|
10
|
+
words.unshift(klass) if prefix_class
|
11
|
+
words.join('_').camelize # logical_id
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# IE: main#protect => MainProtectAuthorizer
|
16
|
+
def authorizer_id(prefix_class: true)
|
17
|
+
return unless authorizer
|
18
|
+
self.class.authorizer_logical_id(authorizer, prefix_class: prefix_class)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Metadata about the authorizer class that can be used later. Stored in the Authorizer template parameters.
|
22
|
+
# In app_class.rb `def controller_params` it is used to build the input parameters for controller templates.
|
23
|
+
def authorizer_metadata
|
24
|
+
klass = authorizer.split("#").first
|
25
|
+
authorizer_class = "#{klass}_authorizer".camelize
|
26
|
+
logical_id = authorizer_id(prefix_class: false)
|
27
|
+
"#{authorizer_class}.#{logical_id}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def authorizer
|
31
|
+
@options[:authorizer]
|
32
|
+
end
|
33
|
+
|
34
|
+
def authorization_type
|
35
|
+
@options[:authorization_type] || inferred_authorization_type
|
36
|
+
end
|
37
|
+
|
38
|
+
def api_key_required
|
39
|
+
@options[:api_key_required]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def inferred_authorization_type
|
44
|
+
return unless authorizer
|
45
|
+
Jets::Authorizer::Base.authorization_type(authorizer)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|