macaw_framework 1.0.1 → 1.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03ac2a8df24de8757381abc1e9a85f7854ac71a49b44d51aa7a23f1d42f95380
4
- data.tar.gz: 4431ce9ab63887660aaa5cc464c00a1dd3a81b08cf4f99bb53926d0aec440fc2
3
+ metadata.gz: 25f8f9d1a39c45e8cab35097244e958621fd566992335198ad0fae2fd482bdbd
4
+ data.tar.gz: 1c0d3558ccd3aaa5b81ee4df1c80227aa558df0a62cc5b82695f0aa58b5b875f
5
5
  SHA512:
6
- metadata.gz: 7f981191cafd8504428de742979979a82ce1609c69ab08afc9f0b8e131381d12cc298b9b7447679b1611719605b825fcf409c9b4afee2b0d0f3a4fa8f28b5c79
7
- data.tar.gz: cda629f9c4c779d11712c18dd5f9b7c8c90805688630d75c14b7fcd7c485b3cdcb2ea2386b5f6bcde62296a7b283337ee45418a08b67e61686f23ef18bf56bb2
6
+ metadata.gz: 25013e659bf86515d3d730a2d8b98c7efdb3f69fc691122e7515b6de38c772d0973d3c23d8dac9824001eb4a34b9ffe99f0c08826f71b14263fdb273b74c0cdb
7
+ data.tar.gz: 872c0b12123044cf2ae34c01e2b6ff05cf1bfaa0698de8d4571fdf8492647b24c88ba415407753423db8dfcd6c6854b5cc4c16b2c856884b4ae260f25c8b6454
data/CHANGELOG.md CHANGED
@@ -47,3 +47,15 @@
47
47
  - Introducing server-side session management
48
48
  - Fixing a bug with cache
49
49
  - Improving README
50
+
51
+ ## [1.0.2] - 2023-05-06
52
+
53
+ - Fixing a bug with cache where ignored_headers where not being properly loaded
54
+ - Fixed a bug with cache where URL parameters were not being considered in the strategy
55
+ - Updating SECURITY.md with more information
56
+
57
+ ## [1.0.3] - 2023-05-10
58
+
59
+ - Fixing issue of error responses being cached
60
+ - Implementing support for min and max SSL version
61
+ - Creating log sanitization to prevent log forging
data/Gemfile CHANGED
@@ -13,6 +13,8 @@ gem "rubocop", "~> 1.21"
13
13
 
14
14
  gem "prometheus-client", "~> 4.1"
15
15
 
16
+ gem "openssl"
17
+
16
18
  group :test do
17
19
  gem "simplecov", "~> 0.21.2"
18
20
  gem "simplecov-json"
data/README.md CHANGED
@@ -56,17 +56,20 @@ m.get('/cached_data', cache: true) do |context|
56
56
  end
57
57
  ```
58
58
 
59
+ Observation: To activate caching you also have to set it's properties on the application.json file. If you don't, caching strategy will not work.
60
+ See section below for configurations.
61
+
59
62
  ### Session management: Handle user sessions securely with server-side in-memory storage
60
63
 
61
64
  ```ruby
62
65
  m.get('/login') do |context|
63
66
  # Authenticate user
64
- context[:session][:user_id] = user_id
67
+ context[:client][:user_id] = user_id
65
68
  end
66
69
 
67
70
  m.get('/dashboard') do |context|
68
71
  # Check if the user is logged in
69
- if context[:session][:user_id]
72
+ if context[:client][:user_id]
70
73
  # Show dashboard
71
74
  else
72
75
  # Redirect to login
@@ -82,6 +85,12 @@ end
82
85
  "port": 8080,
83
86
  "bind": "localhost",
84
87
  "threads": 10,
88
+ "log": {
89
+ "max_length": 1024,
90
+ "sensitive_fields": [
91
+ "password"
92
+ ]
93
+ },
85
94
  "cache": {
86
95
  "cache_invalidation": 3600,
87
96
  "ignore_headers": [
@@ -97,6 +106,8 @@ end
97
106
  "max_requests": 3
98
107
  },
99
108
  "ssl": {
109
+ "min": "SSL3",
110
+ "max": "TLS1.3",
100
111
  "cert_file_name": "path/to/cert/file/file.crt",
101
112
  "key_file_name": "path/to/cert/key/file.key"
102
113
  }
@@ -132,6 +143,8 @@ exists inside `application.json`.
132
143
  If the SSL configuration is provided in the `application.json` file with valid certificate and key files, the TCP server
133
144
  will be wrapped with HTTPS security using the provided certificate.
134
145
 
146
+ The supported values for `min` and `max` in the SSL configuration are: `SSL2`, `SSL3`, `TLS1.1`, `TLS1.2` and `TLS1.3`
147
+
135
148
  If prometheus is enabled, a get endpoint will be defined at path `/metrics` to collect prometheus metrics. This path
136
149
  is configurable via the `application.json` file.
137
150
 
data/SECURITY.md CHANGED
@@ -2,12 +2,26 @@
2
2
 
3
3
  ## Supported Versions
4
4
 
5
+ We are committed to addressing security issues in a timely manner. The following versions of MacawFramework are currently supported with security updates:
6
+
5
7
  | Version | Supported |
6
8
  | ------- | ------------------ |
7
9
  | 1.0.x | :white_check_mark: |
8
10
  | < 1.x | :x: |
9
11
 
10
-
11
12
  ## Reporting a Vulnerability
12
13
 
13
- If you find a vulnerability, please open an issue or send an e-mail to aria.diniz.dev@gmail.com
14
+ We encourage responsible disclosure of security vulnerabilities. If you find a vulnerability in MacawFramework, please follow the steps below:
15
+
16
+ 1. Open an issue on the [GitHub repository](https://github.com/ariasdiniz/macaw_framework/issues) describing the vulnerability. Please include as much detail as possible, such as the affected version, the steps to reproduce the issue, and the potential impact of the vulnerability.
17
+
18
+ Alternatively, you can send an email to aria.diniz.dev@gmail.com with the same information.
19
+
20
+ 2. We will review and acknowledge the report within a reasonable time frame. We may ask for additional information or guidance to help us understand and reproduce the issue.
21
+
22
+ 3. We will work on addressing the vulnerability and will provide updates on the progress.
23
+
24
+ 4. Once the issue is resolved, we will release a new version of MacawFramework with the necessary security fixes.
25
+
26
+ Please remember to follow the project's [Code of Conduct](https://github.com/ariasdiniz/macaw_framework/blob/main/CODE_OF_CONDUCT.md) when reporting security vulnerabilities.
27
+
@@ -12,7 +12,7 @@ module CacheAspect
12
12
  return cache[:cache].cache[cache_filtered_name][0] unless cache[:cache].cache[cache_filtered_name].nil?
13
13
 
14
14
  response = super(*args)
15
- cache[:cache].cache[cache_filtered_name] = [response, Time.now]
15
+ cache[:cache].cache[cache_filtered_name] = [response, Time.now] if should_cache_response?(response[1])
16
16
  response
17
17
  end
18
18
  end
@@ -23,4 +23,8 @@ module CacheAspect
23
23
  filtered_headers = client_data[:headers].filter { |key, _value| !ignored_headers&.include?(key) }
24
24
  [{ body: client_data[:body], params: client_data[:params], headers: filtered_headers }].to_s.to_sym
25
25
  end
26
+
27
+ def should_cache_response?(status)
28
+ (200..299).include?(status.to_i)
29
+ end
26
30
  end
@@ -1,6 +1,7 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: false
2
2
 
3
3
  require "logger"
4
+ require_relative "../data_filters/log_data_filter"
4
5
 
5
6
  ##
6
7
  # This Aspect is responsible for logging
@@ -9,13 +10,17 @@ require "logger"
9
10
  module LoggingAspect
10
11
  def call_endpoint(logger, *args)
11
12
  endpoint_name = args[1].split(".")[1..].join("/")
12
- logger.info("Request received for #{endpoint_name} with arguments: #{args[2..]}")
13
+ logger.info(LogDataFilter.sanitize_for_logging(
14
+ "Request received for #{endpoint_name} with arguments: #{args[2..]}"
15
+ ))
13
16
 
14
17
  begin
15
18
  response = super(*args)
16
- logger.info("Response for #{endpoint_name}: #{response}")
19
+ logger.info(LogDataFilter.sanitize_for_logging("Response for #{endpoint_name}: #{response}"))
17
20
  rescue StandardError => e
18
- logger.error("Error processing #{endpoint_name}: #{e.message}\n#{e.backtrace.join("\n")}")
21
+ logger.error(
22
+ LogDataFilter.sanitize_for_logging("Error processing #{endpoint_name}: #{e.message}\n#{e.backtrace.join("\n")}")
23
+ )
19
24
  raise e
20
25
  end
21
26
 
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../middlewares/memory_invalidation_middleware"
3
4
  require_relative "../middlewares/rate_limiter_middleware"
4
5
  require_relative "../data_filters/response_data_filter"
5
- require_relative "../middlewares/memory_invalidation_middleware"
6
6
  require_relative "../errors/too_many_requests_error"
7
+ require_relative "../utils/supported_ssl_versions"
7
8
  require_relative "../aspects/prometheus_aspect"
8
9
  require_relative "../aspects/logging_aspect"
9
10
  require_relative "../aspects/cache_aspect"
@@ -18,6 +19,8 @@ class Server
18
19
  prepend PrometheusAspect
19
20
  # rubocop:disable Metrics/ParameterLists
20
21
 
22
+ attr_reader :context
23
+
21
24
  ##
22
25
  # Create a new instance of Server.
23
26
  # @param {Macaw} macaw
@@ -123,18 +126,21 @@ class Server
123
126
  end
124
127
 
125
128
  def set_cache_ignored_h
126
- ignored_headers = []
127
- if @macaw.config&.dig("macaw", "cache", "ignored_headers")
128
- ignored_headers = @macaw.config["macaw"]["cache"]["ignore_headers"] || []
129
- end
130
- ignored_headers
129
+ return unless @macaw.config&.dig("macaw", "cache", "ignore_headers")
130
+
131
+ @macaw.config["macaw"]["cache"]["ignore_headers"] || []
131
132
  end
132
133
 
133
134
  def set_ssl
134
- if @macaw.config&.dig("macaw", "ssl")
135
+ ssl_config = @macaw.config["macaw"]["ssl"] if @macaw.config&.dig("macaw", "ssl")
136
+ ssl_config ||= nil
137
+ unless ssl_config.nil?
138
+ version_config = { min: ssl_config["min"], max: ssl_config["max"] }
135
139
  @context = OpenSSL::SSL::SSLContext.new
136
- @context.cert = OpenSSL::X509::Certificate.new(File.read(@macaw.config["macaw"]["ssl"]["cert_file_name"]))
137
- @context.key = OpenSSL::PKey::RSA.new(File.read(@macaw.config["macaw"]["ssl"]["key_file_name"]))
140
+ @context.min_version = SupportedSSLVersions::VERSIONS[version_config[:min]] unless version_config[:min].nil?
141
+ @context.max_version = SupportedSSLVersions::VERSIONS[version_config[:max]] unless version_config[:max].nil?
142
+ @context.cert = OpenSSL::X509::Certificate.new(File.read(ssl_config["cert_file_name"]))
143
+ @context.key = OpenSSL::PKey::RSA.new(File.read(ssl_config["key_file_name"]))
138
144
  end
139
145
  @context ||= nil
140
146
  rescue IOError => e
@@ -166,13 +172,13 @@ class Server
166
172
  {
167
173
  headers: client_data[:headers],
168
174
  body: client_data[:body],
169
- params: client_data[:parameters],
175
+ params: client_data[:params],
170
176
  client: @session[client_ip][0]
171
177
  }
172
178
  )
173
179
  end
174
180
 
175
181
  def get_client_data(body, headers, parameters)
176
- { body: body, headers: headers, parameters: parameters }
182
+ { body: body, headers: headers, params: parameters }
177
183
  end
178
184
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: false
2
+
3
+ require "json"
4
+
5
+ ##
6
+ # Module responsible for sanitizing log data
7
+ module LogDataFilter
8
+ DEFAULT_MAX_LENGTH = 512
9
+ DEFAULT_SENSITIVE_FIELDS = [].freeze
10
+
11
+ def self.config
12
+ @config ||= begin
13
+ file_path = "application.json"
14
+ config = {
15
+ max_length: DEFAULT_MAX_LENGTH,
16
+ sensitive_fields: DEFAULT_SENSITIVE_FIELDS
17
+ }
18
+
19
+ if File.exist?(file_path)
20
+ json = JSON.parse(File.read(file_path))
21
+
22
+ if json["macaw"] && json["macaw"]["log"]
23
+ log_config = json["macaw"]["log"]
24
+ config[:max_length] = log_config["max_length"] if log_config["max_length"]
25
+ config[:sensitive_fields] = log_config["sensitive_fields"] if log_config["sensitive_fields"]
26
+ end
27
+ end
28
+
29
+ config
30
+ end
31
+ end
32
+
33
+ def self.sanitize_for_logging(data, sensitive_fields: config[:sensitive_fields])
34
+ return "" if data.nil?
35
+
36
+ data = data.to_s.force_encoding("UTF-8")
37
+ data = data.gsub(/[\x00-\x1F\x7F]/, "")
38
+ data = data.gsub(/\s+/, " ")
39
+ data = data.slice(0, config[:max_length])
40
+
41
+ sensitive_fields.each do |field|
42
+ next unless data.include?(field.to_s)
43
+
44
+ data = data.gsub(/(#{Regexp.escape(field.to_s)}\s*[:=]\s*)([^\s]+)/) do |_match|
45
+ "#{::Regexp.last_match(1)}#{Digest::SHA256.hexdigest(::Regexp.last_match(2))}"
46
+ end
47
+ end
48
+
49
+ data
50
+ end
51
+ end
@@ -114,6 +114,6 @@ module RequestDataFiltering
114
114
  # Method responsible for sanitizing the parameter value
115
115
  def self.sanitize_parameter_value(value)
116
116
  value.gsub(/[^\w\s]/, "")
117
- value.gsub(/[\r\n\s]/, "")
117
+ value.gsub(/\s/, "")
118
118
  end
119
119
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module SupportedSSLVersions
6
+ VERSIONS = {
7
+ "SSL2" => OpenSSL::SSL::SSL2_VERSION,
8
+ "SSL3" => OpenSSL::SSL::SSL3_VERSION,
9
+ "TLS1.1" => OpenSSL::SSL::TLS1_1_VERSION,
10
+ "TLS1.2" => OpenSSL::SSL::TLS1_2_VERSION,
11
+ "TLS1.3" => OpenSSL::SSL::TLS1_3_VERSION
12
+ }.freeze
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "1.0.1"
4
+ VERSION = "1.0.3"
5
5
  end
@@ -37,7 +37,7 @@ module MacawFramework
37
37
  @prometheus_middleware = PrometheusMiddleware.new if @config["macaw"]["prometheus"]
38
38
  @prometheus_middleware.configure_prometheus(@prometheus, @config, self) if @config["macaw"]["prometheus"]
39
39
  rescue StandardError => e
40
- @macaw_log.error(e.message)
40
+ @macaw_log.warn(e.message)
41
41
  end
42
42
  @port ||= 8080
43
43
  @bind ||= "localhost"
data/main/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @ariasdiniz
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: macaw_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aria Diniz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-03 00:00:00.000000000 Z
11
+ date: 2023-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prometheus-client
@@ -46,6 +46,7 @@ files:
46
46
  - lib/macaw_framework/aspects/logging_aspect.rb
47
47
  - lib/macaw_framework/aspects/prometheus_aspect.rb
48
48
  - lib/macaw_framework/core/server.rb
49
+ - lib/macaw_framework/data_filters/log_data_filter.rb
49
50
  - lib/macaw_framework/data_filters/request_data_filtering.rb
50
51
  - lib/macaw_framework/data_filters/response_data_filter.rb
51
52
  - lib/macaw_framework/errors/endpoint_not_mapped_error.rb
@@ -54,8 +55,10 @@ files:
54
55
  - lib/macaw_framework/middlewares/prometheus_middleware.rb
55
56
  - lib/macaw_framework/middlewares/rate_limiter_middleware.rb
56
57
  - lib/macaw_framework/utils/http_status_code.rb
58
+ - lib/macaw_framework/utils/supported_ssl_versions.rb
57
59
  - lib/macaw_framework/version.rb
58
60
  - macaw_logo.png
61
+ - main/CODEOWNERS
59
62
  - sig/http_status_code.rbs
60
63
  - sig/logging_aspect.rbs
61
64
  - sig/macaw_framework.rbs
@@ -85,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
88
  - !ruby/object:Gem::Version
86
89
  version: '0'
87
90
  requirements: []
88
- rubygems_version: 3.4.12
91
+ rubygems_version: 3.4.10
89
92
  signing_key:
90
93
  specification_version: 4
91
94
  summary: A lightweight back-end web framework