diff --git a/.github/workflows/cahngelog.yml b/.github/workflows/cahngelog.yml deleted file mode 100644 index 8456da8..0000000 --- a/.github/workflows/cahngelog.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Generate changelog -#on: -# release: -# types: [created, edited] - -on: - push: - branches: - - '*' - - '!master' - - -jobs: - generate-changelog: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: BobAnkh/auto-generate-changelog@master - with: - REPO_NAME: 'BobAnkh/auto-generate-changelog' - ACCESS_TOKEN: ${{secrets.GITHUB_TOKEN}} - PATH: '/CHANGELOG.md' - COMMIT_MESSAGE: 'docs(CHANGELOG): update release notes' - TYPE: 'feat:Feature,fix:Bug Fixes,docs:Documentation,refactor:Refactor,perf:Performance Improvements' \ No newline at end of file diff --git a/README.md b/README.md index 3045372..b31a39a 100644 --- a/README.md +++ b/README.md @@ -186,4 +186,27 @@ require 'securenative' options = SecureNative::Options.new(api_key: 'API_KEY', max_events: 10, log_level: 'ERROR', proxy_headers: ['CF-Connecting-IP']) SecureNative::Client.init_with_options(options) -``` \ No newline at end of file +``` + +## Remove PII Data From Headers + +By default, SecureNative SDK remove any known pii headers from the received request. +We also support using custom pii headers and regex matching via configuration, for example: + +### Option 1: Using config file +```yaml +"SECURENATIVE_API_KEY": YOUR_API_KEY +"SECURENATIVE_PII_HEADERS": ["apiKey"] +``` + +Initialize sdk as shown above. + +### Options 2: Using ConfigurationBuilder + +```ruby +require 'securenative' + +options = SecureNative::Options.new(api_key: 'API_KEY', pii_regex_pattern: '((?i)(http_auth_)(\w+)?)') + +SecureNative::Client.init_with_options(options) +``` \ No newline at end of file diff --git a/lib/securenative/config/configuration_builder.rb b/lib/securenative/config/configuration_builder.rb index c762ad1..a11b0cf 100644 --- a/lib/securenative/config/configuration_builder.rb +++ b/lib/securenative/config/configuration_builder.rb @@ -3,12 +3,13 @@ module SecureNative module Config class ConfigurationBuilder - attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers - attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers + attr_reader :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers, :pii_headers, :pii_regex_pattern + attr_writer :api_key, :api_url, :interval, :max_events, :timeout, :auto_send, :disable, :log_level, :fail_over_strategy, :proxy_headers, :pii_headers, :pii_regex_pattern def initialize(api_key: nil, api_url: 'https://api.securenative.com/collector/api/v1', interval: 1000, max_events: 1000, timeout: 1500, auto_send: true, disable: false, log_level: 'FATAL', - fail_over_strategy: SecureNative::FailOverStrategy::FAIL_OPEN, proxy_headers: nil) + fail_over_strategy: SecureNative::FailOverStrategy::FAIL_OPEN, proxy_headers: nil, + pii_headers: nil, pii_regex_pattern: nil) @api_key = api_key @api_url = api_url @interval = interval @@ -19,6 +20,8 @@ def initialize(api_key: nil, api_url: 'https://api.securenative.com/collector/ap @log_level = log_level @fail_over_strategy = fail_over_strategy @proxy_headers = proxy_headers + @pii_headers = pii_headers + @pii_regex_pattern = @pii_regex_pattern end def self.default_securenative_options diff --git a/lib/securenative/config/configuration_manager.rb b/lib/securenative/config/configuration_manager.rb index 3d0bdf5..242b4bd 100644 --- a/lib/securenative/config/configuration_manager.rb +++ b/lib/securenative/config/configuration_manager.rb @@ -50,7 +50,9 @@ def self.load_config disable: _get_env_or_default(properties, 'SECURENATIVE_DISABLE', options.disable), log_level: _get_env_or_default(properties, 'SECURENATIVE_LOG_LEVEL', options.log_level), fail_over_strategy: _get_env_or_default(properties, 'SECURENATIVE_FAILOVER_STRATEGY', options.fail_over_strategy), - proxy_headers: _get_env_or_default(properties, 'SECURENATIVE_PROXY_HEADERS', options.proxy_headers)) + proxy_headers: _get_env_or_default(properties, 'SECURENATIVE_PROXY_HEADERS', options.proxy_headers), + pii_headers: _get_env_or_default(properties, 'SECURENATIVE_PII_HEADERS', options.pii_headers), + pii_regex_pattern: _get_env_or_default(properties, 'SECURENATIVE_REGEX_PATTERN', options.pii_regex_pattern)) end end end diff --git a/lib/securenative/context.rb b/lib/securenative/context.rb index 3f138aa..77e2254 100644 --- a/lib/securenative/context.rb +++ b/lib/securenative/context.rb @@ -27,9 +27,9 @@ def self.from_http_request(request, options) client_token = SecureNative::Frameworks::Hanami.get_client_token(request) if client_token.nil? begin - headers = SecureNative::Frameworks::Rails.get_headers(request) - headers = SecureNative::Frameworks::Sinatra.get_headers(request) if headers.nil? - headers = SecureNative::Frameworks::Hanami.get_headers(request) if headers.nil? + headers = SecureNative::Frameworks::Rails.get_headers(request, options) + headers = SecureNative::Frameworks::Sinatra.get_headers(request, options) if headers.nil? + headers = SecureNative::Frameworks::Hanami.get_headers(request, options) if headers.nil? # Standard Ruby request headers = request.header.to_hash if headers.nil? diff --git a/lib/securenative/frameworks/hanami.rb b/lib/securenative/frameworks/hanami.rb index c9f3217..7356ce4 100644 --- a/lib/securenative/frameworks/hanami.rb +++ b/lib/securenative/frameworks/hanami.rb @@ -4,6 +4,7 @@ module SecureNative module Frameworks class Hanami SECURENATIVE_COOKIE = '_sn' + PII_HEADERS = %w[authorization access_token apikey password passwd secret api_key] def self.get_client_token(request) begin @@ -33,18 +34,51 @@ def self.get_method(request) end end - def self.get_headers(request) + def self.get_headers(request, options) begin headers = {} + if !options.pii_headers.nil? + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(options.pii_headers) && !header.upcase.in?(options.pii_headers) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } - request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| - headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] - } + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(options.pii_headers) && !header.upcase.in?(options.pii_headers) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end + elsif options.pii_regex_pattern.nil? + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + unless options.pii_regex_pattern.match(header) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } - if headers.length == 0 - request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| - headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + unless options.pii_regex_pattern.match(header) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end + else + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(PII_HEADERS) && !header.upcase.in?(PII_HEADERS) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end } + + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(PII_HEADERS) && !header.upcase.in?(PII_HEADERS) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end end return headers rescue StandardError diff --git a/lib/securenative/frameworks/rails.rb b/lib/securenative/frameworks/rails.rb index 8846b0c..4b9e48f 100644 --- a/lib/securenative/frameworks/rails.rb +++ b/lib/securenative/frameworks/rails.rb @@ -4,6 +4,7 @@ module SecureNative module Frameworks class Rails SECURENATIVE_COOKIE = '_sn' + PII_HEADERS = %w[authorization access_token apikey password passwd secret api_key] def self.get_client_token(request) begin @@ -35,18 +36,51 @@ def self.get_method(request) end end - def self.get_headers(request) + def self.get_headers(request, options) begin headers = {} + if !options.pii_headers.nil? + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(options.pii_headers) && !header.upcase.in?(options.pii_headers) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } - request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| - headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] - } + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(options.pii_headers) && !header.upcase.in?(options.pii_headers) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end + elsif options.pii_regex_pattern.nil? + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + unless options.pii_regex_pattern.match(header) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } - if headers.length == 0 - request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| - headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + unless options.pii_regex_pattern.match(header) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end + else + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(PII_HEADERS) && !header.upcase.in?(PII_HEADERS) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end } + + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(PII_HEADERS) && !header.upcase.in?(PII_HEADERS) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end end return headers rescue StandardError diff --git a/lib/securenative/frameworks/sinatra.rb b/lib/securenative/frameworks/sinatra.rb index e043965..b0afc9a 100644 --- a/lib/securenative/frameworks/sinatra.rb +++ b/lib/securenative/frameworks/sinatra.rb @@ -4,6 +4,7 @@ module SecureNative module Frameworks class Sinatra SECURENATIVE_COOKIE = '_sn' + PII_HEADERS = %w[authorization access_token apikey password passwd secret api_key] def self.get_client_token(request) begin @@ -33,18 +34,51 @@ def self.get_method(request) end end - def self.get_headers(request) + def self.get_headers(request, options) begin headers = {} + if !options.pii_headers.nil? + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(options.pii_headers) && !header.upcase.in?(options.pii_headers) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } - request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| - headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] - } + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(options.pii_headers) && !header.upcase.in?(options.pii_headers) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end + elsif options.pii_regex_pattern.nil? + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + unless options.pii_regex_pattern.match(header) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } - if headers.length == 0 - request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| - headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + unless options.pii_regex_pattern.match(header) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end + else + request.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(PII_HEADERS) && !header.upcase.in?(PII_HEADERS) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end } + + if headers.length == 0 + request.headers.env.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }.each { |header| + if !header.downcase.in?(PII_HEADERS) && !header.upcase.in?(PII_HEADERS) + headers[header[0].downcase.gsub("http_", "").gsub("_", "-")] = header[1] + end + } + end end return headers rescue StandardError