From 22f66e88e4be5082b2dd6b60740c3447b5e18749 Mon Sep 17 00:00:00 2001 From: "rasika.abeyrathna" Date: Mon, 2 Sep 2024 15:21:15 +0100 Subject: [PATCH 1/5] GL-919: Implement throttling GL api --- Gemfile | 1 + Gemfile.lock | 3 +++ README.md | 25 +++++++++++++++++++++++++ config/initializers/rack_attack.rb | 25 +++++++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 config/initializers/rack_attack.rb diff --git a/Gemfile b/Gemfile index 217361a13..a77fd8ca6 100644 --- a/Gemfile +++ b/Gemfile @@ -79,5 +79,6 @@ group :test do end group :production do + gem 'rack-attack' gem 'rack-timeout' end diff --git a/Gemfile.lock b/Gemfile.lock index b11e682b1..14114c04a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -291,6 +291,8 @@ GEM activesupport (>= 2.3.14) racc (1.8.0) rack (3.1.7) + rack-attack (6.7.0) + rack (>= 1.0, < 4) rack-protection (4.0.0) base64 (>= 0.1.0) rack (>= 3.0.0, < 4) @@ -501,6 +503,7 @@ DEPENDENCIES pry-rails puma rabl + rack-attack rack-timeout rails (~> 7.1) redis (>= 5.0.6) diff --git a/README.md b/README.md index 8e7844a22..e80ed1477 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,31 @@ TARIFF_SYNC_USERNAME GREEN_LANES_UPDATE_EMAIL ``` +### API Key Management +To access GL endpoints in the production environment, clients must provide a valid API key. These API keys are +securely stored in AWS Secrets Manager under the secret name backend-green-lanes-api-keys as a JSON blob. + +When authorizing a new API client, you should update the JSON file in Secrets Manager by adding the client’s API key +along with their details and the appropriate throttling limits. This ensures that each client has the correct permissions +and rate limits when interacting with the GL endpoints. + +``` +{ + "api_keys": { + "": { + "name": "Descartes Labs", + "description": "Descartes Labs integration key", + "client_id": "dev", + "client_contact": "example@hmrc.com", + "client_secret": "", + "t&c_accepted": true, + "limit": 100, + "period": "1.hour" + } + } +} +``` + ## Licence Trade Tariff is licenced under the [MIT licence](https://github.com/trade-tariff/trade-tariff-backend/blob/main/LICENCE.txt) diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb new file mode 100644 index 000000000..05c1f4380 --- /dev/null +++ b/config/initializers/rack_attack.rb @@ -0,0 +1,25 @@ +if Rails.env.production? + class Rack::Attack + Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(url: ENV['REDIS_URL']) + + API_KEY_LIMITS = JSON.parse(ENV['GREEN_LANES_API_KEYS']) + + + if API_KEY_LIMITS.present? + API_KEY_LIMITS['api_keys'].each do |api_key, config| + throttle("#{api_key}", limit: config['limit'], period: eval(config['period'])) do |req| + # Throttle by API key if the key matches + req.env['HTTP_X_API_KEY'] == api_key + end + end + end + + throttle('default', limit: 10, period: 1.hour) do |req| + # Apply to all requests with an API key not in the list + api_key = req.env['HTTP_X_API_KEY'] + API_KEY_LIMITS.blank? || (!API_KEY_LIMITS.key?(api_key) && api_key.present?) + end + end +else + logger.info 'Rack::Attack is disabled in Dev env.' +end From de923169b8270ef17943762da2c5165476510e94 Mon Sep 17 00:00:00 2001 From: "rasika.abeyrathna" Date: Mon, 2 Sep 2024 15:49:58 +0100 Subject: [PATCH 2/5] GL-919: Implement throttling GL api --- README.md | 4 ++-- config/initializers/rack_attack.rb | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e80ed1477..a158428ce 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ securely stored in AWS Secrets Manager under the secret name backend-green-lanes When authorizing a new API client, you should update the JSON file in Secrets Manager by adding the client’s API key along with their details and the appropriate throttling limits. This ensures that each client has the correct permissions -and rate limits when interacting with the GL endpoints. +and rate limits when interacting with the GL endpoints. Throttling limit is set per number of hours specified in period. ``` { @@ -103,7 +103,7 @@ and rate limits when interacting with the GL endpoints. "client_secret": "", "t&c_accepted": true, "limit": 100, - "period": "1.hour" + "period": 1 } } } diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 05c1f4380..f0419b502 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -4,11 +4,10 @@ class Rack::Attack API_KEY_LIMITS = JSON.parse(ENV['GREEN_LANES_API_KEYS']) - if API_KEY_LIMITS.present? API_KEY_LIMITS['api_keys'].each do |api_key, config| - throttle("#{api_key}", limit: config['limit'], period: eval(config['period'])) do |req| - # Throttle by API key if the key matches + throttle(api_key.to_s, limit: config['limit'], period: config['period'].to_i.hour) do |req| + # Throttle by API key if the key matches req.env['HTTP_X_API_KEY'] == api_key end end From 92242b6c125469ebcd7cd533d92a7aaeefa0136b Mon Sep 17 00:00:00 2001 From: "rasika.abeyrathna" Date: Wed, 4 Sep 2024 11:36:49 +0100 Subject: [PATCH 3/5] GL-919: Implement throttling GL api --- README.md | 25 ------------------------- config/initializers/rack_attack.rb | 11 ++--------- docs/green_lanes_api_keys.md | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 34 deletions(-) create mode 100644 docs/green_lanes_api_keys.md diff --git a/README.md b/README.md index a158428ce..8e7844a22 100644 --- a/README.md +++ b/README.md @@ -84,31 +84,6 @@ TARIFF_SYNC_USERNAME GREEN_LANES_UPDATE_EMAIL ``` -### API Key Management -To access GL endpoints in the production environment, clients must provide a valid API key. These API keys are -securely stored in AWS Secrets Manager under the secret name backend-green-lanes-api-keys as a JSON blob. - -When authorizing a new API client, you should update the JSON file in Secrets Manager by adding the client’s API key -along with their details and the appropriate throttling limits. This ensures that each client has the correct permissions -and rate limits when interacting with the GL endpoints. Throttling limit is set per number of hours specified in period. - -``` -{ - "api_keys": { - "": { - "name": "Descartes Labs", - "description": "Descartes Labs integration key", - "client_id": "dev", - "client_contact": "example@hmrc.com", - "client_secret": "", - "t&c_accepted": true, - "limit": 100, - "period": 1 - } - } -} -``` - ## Licence Trade Tariff is licenced under the [MIT licence](https://github.com/trade-tariff/trade-tariff-backend/blob/main/LICENCE.txt) diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index f0419b502..2b1feff0d 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -6,18 +6,11 @@ class Rack::Attack if API_KEY_LIMITS.present? API_KEY_LIMITS['api_keys'].each do |api_key, config| - throttle(api_key.to_s, limit: config['limit'], period: config['period'].to_i.hour) do |req| - # Throttle by API key if the key matches - req.env['HTTP_X_API_KEY'] == api_key + throttle(api_key.to_s, limit: config['limit'], period: config['period'].to_i.minute) do |req| + req.path.start_with?('/green_lanes/goods_nomenclatures') && req.env['HTTP_X_API_KEY'] == api_key end end end - - throttle('default', limit: 10, period: 1.hour) do |req| - # Apply to all requests with an API key not in the list - api_key = req.env['HTTP_X_API_KEY'] - API_KEY_LIMITS.blank? || (!API_KEY_LIMITS.key?(api_key) && api_key.present?) - end end else logger.info 'Rack::Attack is disabled in Dev env.' diff --git a/docs/green_lanes_api_keys.md b/docs/green_lanes_api_keys.md new file mode 100644 index 000000000..1845960cc --- /dev/null +++ b/docs/green_lanes_api_keys.md @@ -0,0 +1,24 @@ +### API Key Management +To access GL endpoints in the production environment, clients must provide a valid API key. These API keys are +securely stored in AWS Secrets Manager under the secret name backend-green-lanes-api-keys as a JSON blob. + +When authorizing a new API client, you should update the JSON file in Secrets Manager by adding the client’s API key +along with their details and the appropriate throttling limits. This ensures that each client has the correct permissions +and rate limits when interacting with the GL endpoints. Throttling limit is set per number of hours specified in period. + +``` +{ + "api_keys": { + "": { + "name": "Descartes Labs", + "description": "Descartes Labs integration key", + "client_id": "dev", + "client_contact": "example@hmrc.com", + "client_secret": "", + "t&c_accepted": true, + "limit": 100, + "period": 1 + } + } +} +``` \ No newline at end of file From 5891cce0903aabed1eacaae7e7e293773f69e630 Mon Sep 17 00:00:00 2001 From: "rasika.abeyrathna" Date: Wed, 4 Sep 2024 11:46:35 +0100 Subject: [PATCH 4/5] GL-919: Implement throttling GL api --- docs/green_lanes_api_keys.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/green_lanes_api_keys.md b/docs/green_lanes_api_keys.md index 1845960cc..858e8a6da 100644 --- a/docs/green_lanes_api_keys.md +++ b/docs/green_lanes_api_keys.md @@ -1,4 +1,5 @@ ### API Key Management + To access GL endpoints in the production environment, clients must provide a valid API key. These API keys are securely stored in AWS Secrets Manager under the secret name backend-green-lanes-api-keys as a JSON blob. @@ -21,4 +22,4 @@ and rate limits when interacting with the GL endpoints. Throttling limit is set } } } -``` \ No newline at end of file +``` From 6f1e92e8de4f234f0f437da13e72eb2ec3e9aa0e Mon Sep 17 00:00:00 2001 From: "rasika.abeyrathna" Date: Wed, 4 Sep 2024 12:30:12 +0100 Subject: [PATCH 5/5] GL-919: Implement throttling GL api --- config/initializers/rack_attack.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 2b1feff0d..32964b5c5 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -6,7 +6,7 @@ class Rack::Attack if API_KEY_LIMITS.present? API_KEY_LIMITS['api_keys'].each do |api_key, config| - throttle(api_key.to_s, limit: config['limit'], period: config['period'].to_i.minute) do |req| + throttle(api_key.to_s, limit: config['limit'], period: config['period'].to_i.hour) do |req| req.path.start_with?('/green_lanes/goods_nomenclatures') && req.env['HTTP_X_API_KEY'] == api_key end end