jets 2.1.7 → 2.2.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.
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?