jets 2.1.7 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/jets.gemspec +1 -0
  4. data/lib/jets.rb +2 -1
  5. data/lib/jets/application/defaults.rb +3 -0
  6. data/lib/jets/authorizer/base.rb +36 -0
  7. data/lib/jets/authorizer/dsl.rb +64 -0
  8. data/lib/jets/authorizer/helpers/iam_helper.rb +50 -0
  9. data/lib/jets/camelizer.rb +3 -70
  10. data/lib/jets/cfn/builders/api_deployment_builder.rb +0 -2
  11. data/lib/jets/cfn/builders/api_gateway_builder.rb +0 -2
  12. data/lib/jets/cfn/builders/api_resources_builder.rb +0 -2
  13. data/lib/jets/cfn/builders/authorizer_builder.rb +67 -0
  14. data/lib/jets/cfn/builders/base_child_builder.rb +2 -2
  15. data/lib/jets/cfn/builders/controller_builder.rb +4 -1
  16. data/lib/jets/cfn/builders/interface.rb +6 -4
  17. data/lib/jets/cfn/builders/parent_builder.rb +22 -10
  18. data/lib/jets/commands/build.rb +126 -103
  19. data/lib/jets/controller/authorization.rb +72 -0
  20. data/lib/jets/controller/base.rb +25 -43
  21. data/lib/jets/controller/middleware/cors.rb +1 -1
  22. data/lib/jets/klass.rb +3 -3
  23. data/lib/jets/naming.rb +12 -2
  24. data/lib/jets/resource/api_gateway/authorizer.rb +82 -0
  25. data/lib/jets/resource/api_gateway/method.rb +23 -38
  26. data/lib/jets/resource/api_gateway/method/authorization.rb +32 -0
  27. data/lib/jets/resource/child_stack/app_class.rb +16 -5
  28. data/lib/jets/resource/child_stack/authorizer.rb +46 -0
  29. data/lib/jets/resource/child_stack/common_parameters.rb +14 -0
  30. data/lib/jets/resource/child_stack/shared.rb +2 -9
  31. data/lib/jets/router/route.rb +1 -8
  32. data/lib/jets/router/route/authorization.rb +48 -0
  33. data/lib/jets/rule/dsl.rb +0 -1
  34. data/lib/jets/version.rb +1 -1
  35. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6530b12d6ba264316e90060f23d092ff540d9521d7d107524750f68cb939532
4
- data.tar.gz: d64885168c3e73f61a6351b50b8b85f1719d84dd3dbcb7d0972844a2f028264d
3
+ metadata.gz: 00d2f09e01d8be696832564080069855665cf1cc17111d0c6ade401a640bfe09
4
+ data.tar.gz: 4ec605c411378a9c49745298eaa4b73d10478fdbdd7adb012ac32ca9b58e7fa8
5
5
  SHA512:
6
- metadata.gz: 8dedd8a646526a5edc4a678887d069a180eeb7792f414dde66aa5ba02394b27842315a2a43527eebdb7724c2320d39dba63b2ce4a68fbc3f06a64f18ad75ee9a
7
- data.tar.gz: ea63325a12758b511f7908c9b27f0a69fa25109908b7a09af8ec758d65f1646a5f067199e1890407ca2e58f7745428d9af2e0bb05d8dbb6c6675c65980420646
6
+ metadata.gz: 14b548c897b3331ef31b86edede38d1f2b9e4a01c050be39676a839afc5ecfa4b03b911e9da351c98858e8e7b4f063fdacd89425f0dd64540eb6db3ec68e9e34
7
+ data.tar.gz: 16b66bd00dbadf6afc08526a4610dc2b12688986c94425ed93adda048ccf605bf30caf9b8b6b998dc91c9a5c6cea265ec6410c95d3b3c9055914cf2c6227ab77
@@ -3,6 +3,9 @@
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
+ ## [2.2.0]
7
+ - #368 API Gateway Authorizer Concept Support
8
+
6
9
  ## [2.1.7]
7
10
  - #366 fix copy ruby version
8
11
  - fix codebuild for docs
@@ -42,6 +42,7 @@ Gem::Specification.new do |spec|
42
42
  spec.add_dependency "aws-sdk-sns"
43
43
  spec.add_dependency "aws-sdk-sqs"
44
44
  spec.add_dependency "aws-sdk-ssm"
45
+ spec.add_dependency "cfn_camelizer"
45
46
  spec.add_dependency "cfnresponse"
46
47
  spec.add_dependency "dotenv"
47
48
  spec.add_dependency "gems" # jets-gems dependency
@@ -7,10 +7,11 @@ require "active_support/core_ext"
7
7
  require "active_support/dependencies"
8
8
  require "active_support/ordered_hash"
9
9
  require "active_support/ordered_options"
10
+ require "cfn_camelizer"
10
11
  require "fileutils"
12
+ require "jets/gems"
11
13
  require "memoist"
12
14
  require "rainbow/ext/string"
13
- require "jets/gems"
14
15
 
15
16
  require "jets/autoloaders"
16
17
  Jets::Autoloaders.log! if ENV["JETS_AUTOLOAD_LOG"]
@@ -102,6 +102,9 @@ class Jets::Application
102
102
  config.api.endpoint_policy = nil # required when endpoint_type is EDGE
103
103
  config.api.api_key_required = false # Turn off API key required
104
104
 
105
+ config.api.authorizers = ActiveSupport::OrderedOptions.new
106
+ config.api.authorizers.default_token_source = "Auth" # method.request.header.Auth
107
+
105
108
  config.domain = ActiveSupport::OrderedOptions.new
106
109
  # config.domain.name = "#{Jets.project_namespace}.coolapp.com" # Default is nil
107
110
  # config.domain.cert_arn = "..."
@@ -0,0 +1,36 @@
1
+ # Authorizer public methods get turned into Lambda functions.
2
+ #
3
+ # Jets::Authorizer::Base < Jets::Lambda::Functions
4
+ # Both Jets::Authorizer::Base and Jets::Lambda::Functions have Dsl modules included.
5
+ # So the Jets::Authorizer::Dsl overrides some of the Jets::Lambda::Functions behavior.
6
+ module Jets::Authorizer
7
+ class Base < Jets::Lambda::Functions
8
+ include Dsl
9
+ include Helpers::IamHelper
10
+
11
+ class << self
12
+ def process(event, context, meth)
13
+ authorizer = new(event, context, meth)
14
+ authorizer.send(meth)
15
+ end
16
+
17
+ # Parameter: authorizer: Example: "main#protect"
18
+ #
19
+ # Determines authorization_type based on whether the authorizer is a Lambda or Cognito authorizer.
20
+ #
21
+ # Returns custom or cognito_user_pools
22
+ def authorization_type(authorizer)
23
+ class_name, meth = authorizer.split('#')
24
+ klass = "#{class_name}_authorizer".camelize.constantize
25
+ meth = meth.to_sym
26
+
27
+ # If there's a lambda function associated with the authorizer then it's a custom authorizer
28
+ # Otherwise it's a cognito authorizer.
29
+ #
30
+ # Returns: Valid values: none, aws_iam, custom, cognito_user_pools, aws_cross_account_iam
31
+ methods = klass.public_instance_methods(false)
32
+ methods.include?(meth) ? :custom : :cognito_user_pools
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/class'
3
+
4
+ # Jets::Authorizer::Base < Jets::Lambda::Functions
5
+ # Both Jets::Authorizer::Base and Jets::Lambda::Functions have Dsl modules included.
6
+ # So the Jets::Authorizer::Dsl overrides some of the Jets::Lambda::Functions behavior.
7
+ #
8
+ # Implements:
9
+ #
10
+ # default_associated_resource_definition
11
+ #
12
+ module Jets::Authorizer
13
+ module Dsl
14
+ extend ActiveSupport::Concern
15
+
16
+ class_methods do
17
+ def authorizer(props={})
18
+ if props[:type].to_s.upcase == "COGNITO_USER_POOLS"
19
+ cognito_authorizer(props)
20
+ else
21
+ lambda_authorizer(props)
22
+ end
23
+ end
24
+
25
+ def lambda_authorizer(props={})
26
+ with_fresh_properties(multiple_resources: false) do
27
+ r = Jets::Resource::ApiGateway::Authorizer.new(props)
28
+ resource(r.definition) # add associated resource immediately
29
+ end
30
+ end
31
+
32
+ # Creates a task but registers it to cognito_authorizers instead of all_tasks because there is no Lambda
33
+ # function associated with the cognito authorizer.
34
+ def cognito_authorizer(props={})
35
+ resources = [props]
36
+ meth = props[:name]
37
+ resource = Jets::Resource::ApiGateway::Authorizer.new(props)
38
+
39
+ # Mimic task to grab base_replacements, namely namespace.
40
+ # Do not actually use the task to create a Lambda function for cognito authorizer.
41
+ # Only using the task for base_replacements.
42
+ task = Jets::Lambda::Task.new(self.name, meth,
43
+ resources: resources,
44
+ replacements: {}) # No need for need additional replacements. Baseline replacements suffice
45
+ all_cognito_authorizers[name] = { definition: resource.definition, replacements: task.replacements }
46
+ clear_properties
47
+ end
48
+
49
+ def all_cognito_authorizers
50
+ @all_cognito_authorizers ||= ActiveSupport::OrderedHash.new
51
+ end
52
+
53
+ def cognito_authorizers
54
+ all_cognito_authorizers.values
55
+ end
56
+ end
57
+
58
+ included do
59
+ class << self
60
+ include Jets::AwsServices
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,50 @@
1
+ module Jets::Authorizer::Helpers
2
+ module IamHelper
3
+ private
4
+ # Structure: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
5
+ # Example:
6
+ #
7
+ # {
8
+ # "principalId" => "yyyyyyyy", // The principal user identification associated with the token sent by the client.
9
+ # "policyDocument" => {},
10
+ # "context" => {},
11
+ # "usageIdentifierKey" => "{api-key}"
12
+ # }
13
+ #
14
+ def build_policy(*args)
15
+ if args.first.is_a?(Hash) # generalized form
16
+ props = args.first
17
+ else # build_policy(resource, principal, context, usage_identifier_key) form
18
+ resource, principal_id, context, usage_identifier_key = args
19
+ props = {
20
+ principal_id: principal_id || "default_user",
21
+ policy_document: {
22
+ version: "2012-10-17",
23
+ statement: [
24
+ action: "execute-api:Invoke",
25
+ effect: "Allow",
26
+ resource: resource || "*",
27
+ ],
28
+ },
29
+ }
30
+ props[:context] = context if context
31
+ props[:usage_identifier_key] = usage_identifier_key if usage_identifier_key
32
+ end
33
+
34
+ props = Jets::Camelizer.transform(props) # keys get converted from Symbols to Strings as part of this
35
+ # Only top-level keys and keys under context are pascalized
36
+ props.transform_keys! { |k| pascalize(k) }
37
+ if props['context']
38
+ props['context'].transform_keys! { |k| pascalize(k) }
39
+ end
40
+ props
41
+ end
42
+
43
+ def pascalize(value)
44
+ new_value = value.camelize
45
+ first_char = new_value[0..0].downcase
46
+ new_value[0] = first_char
47
+ new_value
48
+ end
49
+ end
50
+ end
@@ -1,71 +1,4 @@
1
- # Custom Camelizer with CloudFormation specific handling.
2
- # Based on: https://stackoverflow.com/questions/8706930/converting-nested-hash-keys-from-camelcase-to-snake-case-in-ruby
1
+ # Wrapper to CfnCamelizer gem
3
2
  module Jets
4
- class Camelizer
5
- class << self
6
- def transform(value, parent_keys=[])
7
- case value
8
- when Array
9
- value.map { |v| transform(v, parent_keys) }
10
- when Hash
11
- initializer = value.map do |k, v|
12
- new_key = camelize_key(k, parent_keys)
13
- [new_key, transform(v, parent_keys+[new_key])]
14
- end
15
- Hash[initializer]
16
- else
17
- value # do not transform values
18
- end
19
- end
20
-
21
- def camelize_key(k, parent_keys=[])
22
- k = k.to_s
23
-
24
- if passthrough?(k, parent_keys)
25
- k # pass through untouch
26
- elsif parent_keys.last == "EventPattern" # top-level
27
- k.dasherize
28
- elsif parent_keys.include?("EventPattern")
29
- # Any keys at 2nd level under EventPattern will be pascalized
30
- pascalize(k)
31
- else
32
- camelize(k)
33
- end
34
- end
35
-
36
- def passthrough?(k, parent_keys)
37
- # do not transform keys anything under these special keys
38
- parent_keys.include?("Variables") ||
39
- parent_keys.include?("ResponseParameters") ||
40
- parent_keys.include?("Fn::Sub") ||
41
- k.include?('-') || k.include?('/')
42
- end
43
-
44
- def camelize(value)
45
- return value if value.is_a?(Integer)
46
-
47
- value = value.to_s.camelize
48
- special_map[value] || value
49
- end
50
-
51
- def pascalize(value)
52
- new_value = value.camelize
53
- first_char = new_value[0..0].downcase
54
- new_value[0] = first_char
55
- new_value
56
- end
57
-
58
- # Some keys have special mappings
59
- def special_map
60
- {
61
- "TemplateUrl" => "TemplateURL",
62
- "Ttl" => "TTL",
63
- "MaxReceiveCount" => "maxReceiveCount",
64
- "DeadLetterTargetArn" => "deadLetterTargetArn",
65
- "DbSubnetGroupDescription" => "DBSubnetGroupDescription",
66
- "DbSubnetGroupName" => "DBSubnetGroupName",
67
- }
68
- end
69
- end
70
- end
71
- end
3
+ Camelizer = CfnCamelizer
4
+ end
@@ -10,8 +10,6 @@ module Jets::Cfn::Builders
10
10
 
11
11
  # compose is an interface method
12
12
  def compose
13
- return unless @options[:templates] || @options[:stack_type] != :minimal
14
-
15
13
  deployment = Jets::Resource::ApiGateway::Deployment.new
16
14
  add_resource(deployment)
17
15
  add_parameters(deployment.parameters)
@@ -10,8 +10,6 @@ module Jets::Cfn::Builders
10
10
 
11
11
  # compose is an interface method
12
12
  def compose
13
- return unless @options[:templates] || @options[:stack_type] != :minimal
14
-
15
13
  add_gateway_routes # "child template": build before add_gateway_rest_api. RestApi logical id and change detection is dependent on it.
16
14
  add_gateway_rest_api # changes parent template
17
15
  add_custom_domain # changes parent template
@@ -10,8 +10,6 @@ module Jets::Cfn::Builders
10
10
 
11
11
  # compose is an interface method
12
12
  def compose
13
- return unless @options[:templates] || @options[:stack_type] != :minimal
14
-
15
13
  add_rest_api_parameter
16
14
  add_gateway_routes
17
15
  end
@@ -0,0 +1,67 @@
1
+ # Implements:
2
+ #
3
+ # compose
4
+ # template_path
5
+ #
6
+ module Jets::Cfn::Builders
7
+ class AuthorizerBuilder < BaseChildBuilder
8
+ include Interface
9
+ include Jets::AwsServices
10
+
11
+ def initialize(path)
12
+ @path = path # IE: app/authorizers/main_authorizer.rb
13
+ @app_class = Jets::Klass.from_path(path)
14
+ @template = ActiveSupport::HashWithIndifferentAccess.new(Resources: {})
15
+ end
16
+
17
+ def compose
18
+ add_common_parameters
19
+ add_api_gateway_parameters
20
+ add_functions
21
+ add_resources
22
+ add_outputs
23
+ # These dont have lambda functions associated with them
24
+ add_cognito_authorizers
25
+ add_cognito_outputs
26
+ end
27
+
28
+ def add_cognito_authorizers
29
+ @app_class.cognito_authorizers.each do |authorizer|
30
+ resource = Jets::Resource.new(authorizer[:definition], authorizer[:replacements])
31
+ add_resource(resource)
32
+ end
33
+ end
34
+
35
+ def add_outputs
36
+ # IE: @app_class = MainAuthorizer
37
+ # The associated resource is the Jets::Resource::ApiGateway::Authorizer definition built during evaluation
38
+ # of the user defined Authorizer class. We'll use it to compute the logical id.
39
+ @app_class.tasks.each do |task|
40
+ authorizer = task.associated_resources.first
41
+ next unless authorizer
42
+
43
+ resource = Jets::Resource.new(authorizer.definition, task.replacements)
44
+ logical_id = resource.logical_id
45
+ add_output(logical_id, value: "!Ref #{logical_id}")
46
+ end
47
+ end
48
+
49
+ def add_cognito_outputs
50
+ @app_class.cognito_authorizers.each do |authorizer|
51
+ resource = Jets::Resource.new(authorizer[:definition], authorizer[:replacements])
52
+ logical_id = resource.logical_id
53
+ add_output(logical_id, value: "!Ref #{logical_id}")
54
+ end
55
+ end
56
+
57
+ def add_api_gateway_parameters
58
+ return if Jets::Router.routes.empty?
59
+
60
+ add_parameter("RestApi", Description: "RestApi")
61
+ end
62
+
63
+ def template_path
64
+ Jets::Naming.authorizer_template_path(@path)
65
+ end
66
+ end
67
+ end
@@ -22,9 +22,9 @@ module Jets::Cfn::Builders
22
22
  end
23
23
 
24
24
  def add_common_parameters
25
- common_parameters = Jets::Resource::ChildStack::AppClass.common_parameters
25
+ common_parameters = Jets::Resource::ChildStack::CommonParameters.common_parameters
26
26
  common_parameters.each do |k,_|
27
- add_parameter(k, Description: k)
27
+ add_parameter(k, Description: k.to_s)
28
28
  end
29
29
 
30
30
  depends_on_params.each do |output_key, output_value|
@@ -19,7 +19,10 @@ module Jets::Cfn::Builders
19
19
  add_parameter("RestApi", Description: "RestApi")
20
20
  scoped_routes.each do |route|
21
21
  resource = Jets::Resource::ApiGateway::Resource.new(route.path)
22
- add_parameter(resource.logical_id, Description: resource.desc)
22
+ add_parameter(resource.logical_id, description: resource.desc)
23
+ if route.authorizer
24
+ add_parameter(route.authorizer_id, description: route.authorizer_metadata)
25
+ end
23
26
  end
24
27
  end
25
28
 
@@ -6,11 +6,13 @@ module Jets::Cfn::Builders
6
6
  module Interface
7
7
  extend Memoist
8
8
 
9
- def build
9
+ def build(parent=false)
10
10
  # Do not bother building or writing the template unless there are functions defined
11
11
  return if @app_class && !@app_class.build?
12
12
 
13
- compose # must be implemented by subclass
13
+ if @options.nil? || @options[:templates] || @options[:stack_type] != :minimal || parent
14
+ compose # must be implemented by subclass
15
+ end
14
16
  write
15
17
  end
16
18
 
@@ -58,7 +60,7 @@ module Jets::Cfn::Builders
58
60
  defaults = { Type: "String" }
59
61
  options = defaults.merge(options)
60
62
  @template[:Parameters] ||= {}
61
- @template[:Parameters][name.to_s.camelize] = options
63
+ @template[:Parameters][name.to_s.camelize] = Jets::Camelizer.transform(options)
62
64
  end
63
65
 
64
66
  def add_outputs(attributes)
@@ -69,7 +71,7 @@ module Jets::Cfn::Builders
69
71
 
70
72
  def add_output(name, options={})
71
73
  @template[:Outputs] ||= {}
72
- @template[:Outputs][name.camelize] = options
74
+ @template[:Outputs][name.camelize] = Jets::Camelizer.transform(options)
73
75
  end
74
76
 
75
77
  def add_resources
@@ -41,28 +41,35 @@ module Jets::Cfn::Builders
41
41
  end
42
42
 
43
43
  def build_child_resources
44
- expression = "#{Jets::Naming.template_path_prefix}-app-*"
45
- # IE: path: #{Jets.build_root}/templates/demo-dev-2-comments_controller.yml
46
- Dir.glob(expression).each do |path|
47
- next unless File.file?(path)
44
+ for_each_path(:app) do |path|
48
45
  add_app_class_stack(path)
49
46
  end
50
-
51
- expression = "#{Jets::Naming.template_path_prefix}-shared-*"
52
- # IE: path: #{Jets.build_root}/templates/demo-dev-2-shared-resources.yml
53
- Dir.glob(expression).each do |path|
54
- next unless File.file?(path)
55
-
47
+ for_each_path(:shared) do |path|
56
48
  add_shared_resources(path)
57
49
  end
58
50
 
59
51
  if full? and !Jets::Router.routes.empty?
52
+ for_each_path(:authorizers) do |path|
53
+ add_authorizer_resources(path)
54
+ end
60
55
  add_api_gateway
61
56
  add_api_resources
62
57
  add_api_deployment
63
58
  end
64
59
  end
65
60
 
61
+ # Example paths:
62
+ # #{Jets.build_root}/templates/demo-dev-2-shared-resources.yml
63
+ # #{Jets.build_root}/templates/demo-dev-2-app-comments_controller.yml
64
+ # #{Jets.build_root}/templates/demo-dev-2-authorizers-main_authorizer.yml
65
+ def for_each_path(type)
66
+ expression = "#{Jets::Naming.template_path_prefix}-#{type}-*"
67
+ Dir.glob(expression).each do |path|
68
+ next unless File.file?(path)
69
+ yield(path)
70
+ end
71
+ end
72
+
66
73
  def full?
67
74
  @options[:templates] || @options[:stack_type] == :full
68
75
  end
@@ -73,6 +80,11 @@ module Jets::Cfn::Builders
73
80
  add_child_resources(resource)
74
81
  end
75
82
 
83
+ def add_authorizer_resources(path)
84
+ resource = Jets::Resource::ChildStack::Authorizer.new(@options[:s3_bucket], path: path)
85
+ add_child_resources(resource)
86
+ end
87
+
76
88
  def add_shared_resources(path)
77
89
  resource = Jets::Resource::ChildStack::Shared.new(@options[:s3_bucket], path: path)
78
90
  add_child_resources(resource) if resource.resources?