diff --git a/deploy/apigee/deploy_apigee.sh b/deploy/apigee/deploy_apigee.sh index d5da633c0..840d8dd3e 100755 --- a/deploy/apigee/deploy_apigee.sh +++ b/deploy/apigee/deploy_apigee.sh @@ -49,6 +49,7 @@ function prep_proxies() { copy_resources "$proxy_name" "policies" "policies" copy_resources "$proxy_name" "proxy_endpoints" "proxies" copy_resources "$proxy_name" "target_endpoints" "targets" + copy_resources "$proxy_name" "resources" "resources" cd "$ENV_TMP_DIR" mv "$proxy_name" apiproxy # Set a constant modification timestamp on all files so zip archive hash @@ -75,6 +76,8 @@ function copy_resources() { # Copies a single file for the given proxy. Looks in the given source dir for a # file with the given source file name. If the file name has the format # *.template.xml, substitutes environment variables for REPLACE_WITH_ clauses. +# If the filename includes a path (e.g. jsc/quota.js), this creates +# the necessary directory structure. function copy_file() { proxy_name="$1" source_dir="$2" @@ -101,8 +104,14 @@ function copy_file() { fi sed -i "" "s/REPLACE_WITH_${var_name}/${!var_name}/g" "$write_file" done + elif [[ -f "$source_dir/$source_file" ]]; then + write_file="$write_dir/$source_file" + if [[ "$source_file" == *"/"* ]]; then + mkdir -p "$write_dir/$(dirname "$source_file")" + fi + cp "$source_dir/$source_file" "$write_file" else - echo "Not found: $source_dir/$source_file.xml" + echo "Not found: $source_dir/$source_file(.xml|.template.xml)" fi } diff --git a/deploy/apigee/envs/nonprod.yaml b/deploy/apigee/envs/nonprod.yaml index a7d44b612..ecde98fc0 100644 --- a/deploy/apigee/envs/nonprod.yaml +++ b/deploy/apigee/envs/nonprod.yaml @@ -7,6 +7,8 @@ proxies: - rewrite-missing-key-message - rewrite-missing-allow-header-error - enforce-quota-limit + - enforce-quota-limit-v2 + - set-quota-tier - rewrite-quota-exceeded-message - set-southbound-key - strip-api-key-header-and-params @@ -14,6 +16,8 @@ proxies: - api target_endpoints: - api + resources: + - jsc/set-quota-tier.js - name: bard policies: - forward-host diff --git a/deploy/apigee/envs/prod.yaml b/deploy/apigee/envs/prod.yaml index d39f91154..58e0d7833 100644 --- a/deploy/apigee/envs/prod.yaml +++ b/deploy/apigee/envs/prod.yaml @@ -7,6 +7,8 @@ proxies: - rewrite-missing-key-message - rewrite-missing-allow-header-error - enforce-quota-limit + - enforce-quota-limit-v2 + - set-quota-tier - rewrite-quota-exceeded-message - set-southbound-key - strip-api-key-header-and-params @@ -14,6 +16,8 @@ proxies: - api target_endpoints: - api + resources: + - jsc/set-quota-tier.js - name: bard policies: - forward-host @@ -30,4 +34,4 @@ proxies: proxy_endpoints: - nl target_endpoints: - - nl + - nl \ No newline at end of file diff --git a/deploy/apigee/policies/enforce-quota-limit-v2.xml b/deploy/apigee/policies/enforce-quota-limit-v2.xml new file mode 100644 index 000000000..55283b215 --- /dev/null +++ b/deploy/apigee/policies/enforce-quota-limit-v2.xml @@ -0,0 +1,11 @@ + + + enforce-quota-limit-v2 + + + 1 + month + true + false + Enforce + diff --git a/deploy/apigee/policies/set-quota-tier.template.xml b/deploy/apigee/policies/set-quota-tier.template.xml new file mode 100644 index 000000000..1d96b88af --- /dev/null +++ b/deploy/apigee/policies/set-quota-tier.template.xml @@ -0,0 +1,13 @@ + + + set-quota-tier + + REPLACE_WITH_DEFAULT_QUOTA_REQUESTS_PER_INTERVAL + REPLACE_WITH_DEFAULT_QUOTA_INTERVAL + REPLACE_WITH_DEFAULT_QUOTA_TIMEUNIT + REPLACE_WITH_TRIAL_API_KEY + REPLACE_WITH_TRIAL_KEY_QUOTA_REQUESTS_PER_INTERVAL + REPLACE_WITH_TRIAL_KEY_QUOTA_INTERVAL_MINUTES + + jsc://set-quota-tier.js + diff --git a/deploy/apigee/proxies/api.xml b/deploy/apigee/proxies/api.xml index 46968ebb0..0340282f8 100644 --- a/deploy/apigee/proxies/api.xml +++ b/deploy/apigee/proxies/api.xml @@ -9,6 +9,8 @@ rewrite-missing-key-message rewrite-missing-allow-header-error enforce-quota-limit + enforce-quota-limit-v2 + set-quota-tier rewrite-quota-exceeded-message set-southbound-key strip-api-key-header-and-params @@ -16,6 +18,9 @@ api + + jsc://set-quota-tier.js + api diff --git a/deploy/apigee/resources/jsc/set-quota-tier.js b/deploy/apigee/resources/jsc/set-quota-tier.js new file mode 100644 index 000000000..8d3839ad8 --- /dev/null +++ b/deploy/apigee/resources/jsc/set-quota-tier.js @@ -0,0 +1,92 @@ +const DC_ENFORCE_QUOTA_FLOW_VAR = "datacommons_enforce_quota"; +const DC_QUOTA_TIER_FLOW_VAR = "datacommons_quota_tier"; +const DC_QUOTA_REQUESTS_PER_INTERVAL_FLOW_VAR = "datacommons_quota_requests_per_interval"; +const DC_QUOTA_INTERVAL_FLOW_VAR = "datacommons_quota_interval"; +const DC_QUOTA_TIMEUNIT_FLOW_VAR = "datacommons_quota_timeunit"; +const DC_QUOTA_IDENTIFIER_FLOW_VAR = "datacommons_quota_identifier"; + +const DC_VERIFY_API_KEY_POLICY_NAME = "verify-api-key-in-header"; +const VERIFY_API_KEY_FLOW_VAR_PREFIX = "verifyapikey." + DC_VERIFY_API_KEY_POLICY_NAME; +const DEVELOPER_QUOTA_TIER_FLOW_VAR = VERIFY_API_KEY_FLOW_VAR_PREFIX + ".developer.datacommons_quota_tier"; +const DEVLOPER_EMAIl_FLOW_VAR = VERIFY_API_KEY_FLOW_VAR_PREFIX + ".developer.email"; +const API_KEY_FLOW_VAR = VERIFY_API_KEY_FLOW_VAR_PREFIX + ".client_id"; +const TRIAL_API_KEY_QUOTA_TIER = "trial-api-key"; +const DEFAULT_QUOTA_TIER = "default"; +const UNLIMITED_QUOTA_TIER = "unlimited"; +const ENFORCE_QUOTA_HEADER = "datacommons-enforce-quota"; + +function is_quota_enforcement_enabled() { + // Skip enforcement if api key not found + var api_key = context.getVariable(API_KEY_FLOW_VAR); + if (!api_key || api_key.trim().length == 0) { + return false; + } + var enforce_quota_header = context.proxyRequest.headers[ENFORCE_QUOTA_HEADER]; + return enforce_quota_header != undefined && enforce_quota_header != null && enforce_quota_header == 'true' +} + +function is_trial_api_key() { + var api_key = context.getVariable(API_KEY_FLOW_VAR); + return api_key && api_key.trim() == properties.trial_api_key; +} + +function get_quota_tier() { + var quota_tier = DEFAULT_QUOTA_TIER; + if (is_trial_api_key()) { + quota_tier = TRIAL_API_KEY_QUOTA_TIER; + } else { + var developer_quota_tier = context.getVariable(DEVELOPER_QUOTA_TIER_FLOW_VAR); + print("Developer quota tier=[" + developer_quota_tier + "]"); + if (developer_quota_tier && developer_quota_tier.trim().length > 0) { + quota_tier = developer_quota_tier.trim(); + } + } + return quota_tier; +} + +function enforce_quota() { + if (!is_quota_enforcement_enabled()) { + context.setVariable(DC_ENFORCE_QUOTA_FLOW_VAR, "false"); + return; + } + + var quota_tier = get_quota_tier(); + print("Quota tier=[" + quota_tier + "]"); + var enforce_quota = "true"; + var request_per_interval = ""; + var quota_interval = ""; + var quota_timeunit = ""; + var quota_identifier = ""; + switch (quota_tier) { + case UNLIMITED_QUOTA_TIER: + enforce_quota = "false"; + quota_tier = UNLIMITED_QUOTA_TIER; + break; + case TRIAL_API_KEY_QUOTA_TIER: + enforce_quota = "true"; + quota_tier = TRIAL_API_KEY_QUOTA_TIER; + quota_identifier = context.getVariable("proxy.client.ip"); + request_per_interval = properties.trial_key_quota_requests_per_interval; + quota_interval = properties.trial_key_quota_interval_minutes; + quota_timeunit = "minute"; + break; + default: + enforce_quota = "true"; + quota_tier = DEFAULT_QUOTA_TIER; + quota_identifier = context.getVariable(DEVLOPER_EMAIl_FLOW_VAR); + request_per_interval = properties.default_quota_requests_per_interval; + quota_interval = properties.default_quota_interval; + quota_timeunit = properties.default_quota_timeunit; + break; + } + context.setVariable(DC_ENFORCE_QUOTA_FLOW_VAR, enforce_quota); + context.setVariable(DC_QUOTA_TIER_FLOW_VAR, quota_tier); + if (enforce_quota == "true") { + context.setVariable(DC_QUOTA_IDENTIFIER_FLOW_VAR, quota_identifier); + context.setVariable(DC_QUOTA_REQUESTS_PER_INTERVAL_FLOW_VAR, request_per_interval); + context.setVariable(DC_QUOTA_INTERVAL_FLOW_VAR, quota_interval); + context.setVariable(DC_QUOTA_TIMEUNIT_FLOW_VAR, quota_timeunit); + } +} + +enforce_quota(); \ No newline at end of file diff --git a/deploy/apigee/sample.env b/deploy/apigee/sample.env index fda4ccf06..95fd5b09d 100644 --- a/deploy/apigee/sample.env +++ b/deploy/apigee/sample.env @@ -6,3 +6,6 @@ TRIAL_API_KEY= TRIAL_KEY_QUOTA_INTERVAL_MINUTES= API_PORTAL_URL= +DEFAULT_QUOTA_REQUESTS_PER_INTERVAL= +DEFAULT_QUOTA_INTERVAL= +DEFAULT_QUOTA_TIMEUNIT= diff --git a/deploy/apigee/target_endpoints/api.template.xml b/deploy/apigee/target_endpoints/api.template.xml index 69ce57187..8f4ed692c 100644 --- a/deploy/apigee/target_endpoints/api.template.xml +++ b/deploy/apigee/target_endpoints/api.template.xml @@ -66,14 +66,31 @@ verify-api-key-in-header + + + (client_id != null) AND + (client_id != "") + + set-quota-tier + (client_id != null) AND (client_id != "") AND - (client_id = "REPLACE_WITH_TRIAL_API_KEY") + (client_id = "REPLACE_WITH_TRIAL_API_KEY") AND + + + ! (datacommons_enforce_quota != null AND datacommons_enforce_quota = "true") enforce-quota-limit - + + + + (datacommons_enforce_quota != null) AND + (datacommons_enforce_quota = "true") + + enforce-quota-limit-v2 +