Skip to content

Commit 0e93717

Browse files
author
Dmytro Trotsko
committed
Fixed logging
1 parent 69a1ffe commit 0e93717

File tree

4 files changed

+87
-25
lines changed

4 files changed

+87
-25
lines changed

src/assets/js/indicatorHandler.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,6 @@ class IndicatorHandler {
365365
$("#geographic_value").select2("data"),
366366
({ geoType }) => [geoType]
367367
);
368-
console.log(covidCastGeographicValues);
369368
const fluviewLocations = $("#fluviewLocations").select2("data");
370369
const nidssFluLocations = $("#nidssFluLocations").select2("data");
371370
const nidssDengueLocations = $("#nidssDengueLocations").select2("data");
@@ -378,6 +377,7 @@ class IndicatorHandler {
378377
nidssDengueLocations: nidssDengueLocations,
379378
flusurvLocations: flusurvLocations,
380379
apiKey: document.getElementById("apiKey").value,
380+
clientId: clientId,
381381
};
382382
const csrftoken = Cookies.get("csrftoken");
383383
$.ajax({
@@ -426,6 +426,7 @@ class IndicatorHandler {
426426
nidssDengueLocations: nidssDengueLocations,
427427
flusurvLocations: flusurvLocations,
428428
apiKey: document.getElementById("apiKey").value,
429+
clientId: clientId,
429430
}
430431
const csrftoken = Cookies.get("csrftoken");
431432
$.ajax({
@@ -476,6 +477,7 @@ class IndicatorHandler {
476477
nidssDengueLocations: nidssDengueLocations,
477478
flusurvLocations: flusurvLocations,
478479
apiKey: document.getElementById("apiKey").value,
480+
clientId: clientId,
479481
}
480482
const csrftoken = Cookies.get("csrftoken");
481483
$.ajax({
@@ -526,6 +528,7 @@ class IndicatorHandler {
526528
nidssDengueLocations: nidssDengueLocations,
527529
flusurvLocations: flusurvLocations,
528530
apiKey: document.getElementById("apiKey").value,
531+
clientId: clientId,
529532
}
530533
const csrftoken = Cookies.get("csrftoken");
531534
var createQueryCodePython = `<h4>PYTHON PACKAGE</h4>`

src/epiportal/settings.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,38 @@
342342
# django docs
343343
# https://django-docs.readthedocs.io/en/latest/
344344
DOCS_ROOT = os.path.join(BASE_DIR, 'docs', 'build', 'html')
345+
346+
347+
"""
348+
REVERSE_PROXY_DEPTH is an integer value that indicates how many "chained" and trusted reverse proxies (like nginx) this
349+
server instance is running behind. "chained" refers to proxies forwarding to other proxies, and then ultimately
350+
forwarding to the app server itself. each of these proxies appends the remote address of the request to the
351+
"X-Forwarded-For" header. in many situations, the most externally facing proxy (the first in the chain, the one that
352+
faces the "open internet") can and should be set to write its own "X-Forwarded-For" header, ignoring and replacing
353+
(or creating anew, if it didnt exist) such a header from the client request -- thus preserving the chain of trusted
354+
proxies under our control.
355+
356+
however, in our typical production environment, the most externally facing "proxy" is the AWS application load balancer,
357+
which seemingly cannot be configured to provide this trust boundary without losing the referring client address
358+
(see: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/x-forwarded-headers.html ). accordingly, in
359+
our current typical production environment, REVERSE_PROXY_DEPTH should be set to "2": one for the AWS application load
360+
balancer, and one for our own nginx/haproxy instance. thus "2" is our default value.
361+
362+
it is important that REVERSE_PROXY_DEPTH is set accurately for two reasons...
363+
364+
setting it too high (or to -1) will respect more of the entries in the "X-Forwarded-For" header than are appropriate.
365+
this can allow remote addresses to be "spoofed" when a client fakes this header, carrying security/identity
366+
implications. in dev and testing, it is not particularly dangerous for this variable to be set to -1 (special case
367+
for an "infinite" depth, where any and all proxy hops will be trusted).
368+
369+
setting it too low can hinder logging accuracy -- that can cause an intermediate proxy IP address to be used as the
370+
"real" client IP address, which could cause requests to be rate-limited inappropriately.
371+
372+
setting REVERSE_PROXY_DEPTH to "0" essentially indicates there are no proxies between this server and the outside
373+
world. in this case, the "X-Forwarded-For" header is ignored.
374+
"""
375+
REVERSE_PROXY_DEPTH = int(os.environ.get("PROXY_DEPTH", 4))
376+
# TODO: ^ this value should be "4" for the prod CC API server processes, and is currently unclear
377+
# for prod AWS API server processes (but should be the same or lower)... when thats properly
378+
# determined, set the default to the minimum of the two environments and special case the
379+
# other in conf file(s).

src/indicatorsets/utils.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@
88
from django.conf import settings
99
from django.http import JsonResponse
1010
from epiweeks import Week
11+
from delphi_utils import get_structured_logger
1112

1213
from indicatorsets.models import IndicatorSet
1314

1415
FLUVIEW_INDICATORS_MAPPING = {"wili": "%wILI", "ili": "%ILI"}
1516

17+
form_data_logger = get_structured_logger("form_data_logger")
18+
form_stats_logger = get_structured_logger("form_stats_logger")
19+
1620

1721
def list_to_dict(lst):
1822
result = {}
@@ -633,16 +637,37 @@ def generate_query_code_flusurv(flusurv_geos, start_date, end_date):
633637
return python_code_blocks, r_code_blocks
634638

635639

636-
def get_client_ip(request):
637-
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
638-
return (
639-
x_forwarded_for.split(",")[0]
640-
if x_forwarded_for
641-
else request.META.get("REMOTE_ADDR")
642-
)
640+
def get_real_ip_addr(req): # `req` should be a Flask.request object
641+
if settings.REVERSE_PROXY_DEPTH:
642+
# we only expect/trust (up to) "REVERSE_PROXY_DEPTH" number of proxies between this server and the outside world.
643+
# a REVERSE_PROXY_DEPTH of 0 means not proxied, i.e. server is globally directly reachable.
644+
# a negative proxy depth is a special case to trust the whole chain -- not generally recommended unless the
645+
# most-external proxy is configured to disregard "X-Forwarded-For" from outside.
646+
# really, ONLY trust the following headers if reverse proxied!!!
647+
x_forwarded_for = req.META.get('HTTP_X_FORWARDED_FOR')
648+
649+
if x_forwarded_for:
650+
full_proxy_chain = x_forwarded_for.split(",")
651+
# eliminate any extra addresses at the front of this list, as they could be spoofed.
652+
if settings.REVERSE_PROXY_DEPTH > 0:
653+
depth = settings.REVERSE_PROXY_DEPTH
654+
else:
655+
# special case for -1/negative: setting `depth` to 0 will not strip any items from the chain
656+
depth = 0
657+
trusted_proxy_chain = full_proxy_chain[-depth:]
658+
# accept the first (or only) address in the remaining trusted part of the chain as the actual remote address
659+
return trusted_proxy_chain[0].strip()
660+
661+
# fall back to "X-Real-Ip" if "X-Forwarded-For" isnt present
662+
x_real_ip = req.META.get('HTTP_X_REAL_IP')
663+
if x_real_ip:
664+
return x_real_ip
665+
666+
# if we are not proxied (or we are proxied but the headers werent present and we fell through to here), just use the remote ip addr as the true client address
667+
return req.META.get('REMOTE_ADDR')
643668

644669

645-
def log_form_stats(request, data, form_mode, logger):
670+
def log_form_stats(request, data, form_mode):
646671
log_data = {
647672
"form_mode": form_mode,
648673
"num_of_indicators": len(data.get("indicators", [])),
@@ -660,14 +685,15 @@ def log_form_stats(request, data, form_mode, logger):
660685
),
661686
"api_key_used": bool(data.get("api_key")),
662687
"api_key": data.get("api_key", "")[:4] + "..." if data.get("api_key") else "",
663-
"user_ip": get_client_ip(request),
688+
"user_ip": get_real_ip_addr(request),
664689
"user_ga_id": data.get("clientId", "") if data.get("clientId") else "",
665690
}
691+
form_stats_logger.info("form_stats", clientId=data.get("clientId", ""))
666692

667-
logger.info(log_data)
693+
form_stats_logger.info("form_stats", **log_data)
668694

669695

670-
def log_form_data(request, data, form_mode, logger):
696+
def log_form_data(request, data, form_mode):
671697
indicators = data.get("indicators", [])
672698
indicators = [
673699
{
@@ -735,7 +761,7 @@ def log_form_data(request, data, form_mode, logger):
735761
"epiweeks": get_epiweek(data.get("start_date", ""), data.get("end_date", "")) if data.get("start_date") and data.get("end_date") else [], # fmt: skip
736762
"api_key_used": bool(data.get("apiKey")),
737763
"api_key": data.get("apiKey", "") if data.get("apiKey") else "",
738-
"user_ip": get_client_ip(request),
764+
"user_ip": get_real_ip_addr(request),
739765
"user_ga_id": data.get("clientId", "") if data.get("clientId") else "",
740766
}
741-
logger.info(log_data)
767+
form_data_logger.info("form_data", **log_data)

src/indicatorsets/views.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,13 @@
3636
generate_query_code_nidss_dengue,
3737
generate_query_code_flusurv,
3838
log_form_data,
39-
log_form_stats
39+
log_form_stats,
4040
)
4141

4242
from delphi_utils import get_structured_logger
4343

4444
indicatorsets_logger = get_structured_logger("indicatorsets_logger")
4545

46-
form_data_logger = get_structured_logger("form_data_logger")
47-
form_stats_logger = get_structured_logger("form_stats_logger")
4846

4947
HEADER_DESCRIPTION = "Discover, display and download real-time infectious disease indicators (time series) that track a variety of pathogens, diseases and syndromes in a variety of locations (primarily within the USA). Browse the list, or filter it first by locations and pathogens of interest, by surveillance categories, and more. Expand any row to expose and select from a set of related indicators, then hit 'Show Selected Indicators' at bottom to plot or export your selected indicators, or to generate code snippets to retrieve them from the Delphi Epidata API. Most indicators are served from the Delphi Epidata real-time repository, but some may be available only from third parties or may require prior approval."
5048

@@ -256,8 +254,8 @@ def epivis(request):
256254
nidss_flu_locations = data.get("nidssFluLocations", [])
257255
nidss_dengue_locations = data.get("nidssDengueLocations", [])
258256
flusurv_locations = data.get("flusurvLocations", [])
259-
log_form_stats(request, data, "epivis", form_stats_logger)
260-
log_form_data(request, data, "epivis", form_data_logger)
257+
log_form_stats(request, data, "epivis")
258+
log_form_data(request, data, "epivis")
261259
for indicator in indicators:
262260
if indicator["_endpoint"] == "covidcast":
263261
datasets.extend(
@@ -306,8 +304,8 @@ def generate_export_data_url(request):
306304
flusurv_locations = data.get("flusurvLocations", [])
307305
api_key = data.get("apiKey", None)
308306

309-
log_form_stats(request, data, "export", form_stats_logger)
310-
log_form_data(request, data, "export", form_data_logger)
307+
log_form_stats(request, data, "export")
308+
log_form_data(request, data, "export")
311309
data_export_commands.extend(
312310
generate_covidcast_indicators_export_url(
313311
indicators, start_date, end_date, covidcast_geos, api_key
@@ -348,8 +346,8 @@ def generate_export_data_url(request):
348346
def preview_data(request):
349347
if request.method == "POST":
350348
data = json.loads(request.body)
351-
log_form_stats(request, data, "preview", form_stats_logger)
352-
log_form_data(request, data, "preview", form_data_logger)
349+
log_form_stats(request, data, "preview")
350+
log_form_data(request, data, "preview")
353351
start_date = data.get("start_date", "")
354352
end_date = data.get("end_date", "")
355353
indicators = data.get("indicators", [])
@@ -392,8 +390,8 @@ def preview_data(request):
392390
def create_query_code(request):
393391
if request.method == "POST":
394392
data = json.loads(request.body)
395-
log_form_stats(request, data, "code", form_stats_logger)
396-
log_form_data(request, data, "code", form_data_logger)
393+
log_form_stats(request, data, "code")
394+
log_form_data(request, data, "code")
397395
start_date = data.get("start_date", "")
398396
end_date = data.get("end_date", "")
399397
indicators = data.get("indicators", [])

0 commit comments

Comments
 (0)