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 +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile +2 -0
- data/README.md +15 -2
- data/SECURITY.md +16 -2
- data/lib/macaw_framework/aspects/cache_aspect.rb +5 -1
- data/lib/macaw_framework/aspects/logging_aspect.rb +9 -4
- data/lib/macaw_framework/core/server.rb +17 -11
- data/lib/macaw_framework/data_filters/log_data_filter.rb +51 -0
- data/lib/macaw_framework/data_filters/request_data_filtering.rb +1 -1
- data/lib/macaw_framework/utils/supported_ssl_versions.rb +13 -0
- data/lib/macaw_framework/version.rb +1 -1
- data/lib/macaw_framework.rb +1 -1
- data/main/CODEOWNERS +1 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25f8f9d1a39c45e8cab35097244e958621fd566992335198ad0fae2fd482bdbd
|
|
4
|
+
data.tar.gz: 1c0d3558ccd3aaa5b81ee4df1c80227aa558df0a62cc5b82695f0aa58b5b875f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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[:
|
|
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[:
|
|
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
|
|
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:
|
|
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(
|
|
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(
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
137
|
-
@context.
|
|
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[:
|
|
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,
|
|
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
|
|
@@ -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
|
data/lib/macaw_framework.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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-
|
|
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.
|
|
91
|
+
rubygems_version: 3.4.10
|
|
89
92
|
signing_key:
|
|
90
93
|
specification_version: 4
|
|
91
94
|
summary: A lightweight back-end web framework
|