347 lines
14 KiB
Python
347 lines
14 KiB
Python
# Copyright The IETF Trust 2007-2024, All Rights Reserved
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from base64 import b64decode
|
|
from email.utils import parseaddr
|
|
import json
|
|
|
|
from ietf import __release_hash__
|
|
from ietf.settings import * # pyflakes:ignore
|
|
from ietf.settings import STORAGES, MORE_STORAGE_NAMES, BLOBSTORAGE_CONNECT_TIMEOUT, BLOBSTORAGE_READ_TIMEOUT, BLOBSTORAGE_MAX_ATTEMPTS
|
|
import botocore.config
|
|
|
|
|
|
def _multiline_to_list(s):
|
|
"""Helper to split at newlines and conver to list"""
|
|
return [item.strip() for item in s.split("\n")]
|
|
|
|
|
|
# Default to "development". Production _must_ set DATATRACKER_SERVER_MODE="production" in the env!
|
|
SERVER_MODE = os.environ.get("DATATRACKER_SERVER_MODE", "development")
|
|
|
|
# Use X-Forwarded-Proto to determine request.is_secure(). This relies on CloudFlare overwriting the
|
|
# value of the header if an incoming request sets it, which it does:
|
|
# https://developers.cloudflare.com/fundamentals/reference/http-request-headers/#x-forwarded-proto
|
|
# See also, especially the warnings:
|
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
|
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
|
|
# Secrets
|
|
_SECRET_KEY = os.environ.get("DATATRACKER_DJANGO_SECRET_KEY", None)
|
|
if _SECRET_KEY is not None:
|
|
SECRET_KEY = _SECRET_KEY
|
|
else:
|
|
raise RuntimeError("DATATRACKER_DJANGO_SECRET_KEY must be set")
|
|
|
|
_NOMCOM_APP_SECRET_B64 = os.environ.get("DATATRACKER_NOMCOM_APP_SECRET_B64", None)
|
|
if _NOMCOM_APP_SECRET_B64 is not None:
|
|
NOMCOM_APP_SECRET = b64decode(_NOMCOM_APP_SECRET_B64)
|
|
else:
|
|
raise RuntimeError("DATATRACKER_NOMCOM_APP_SECRET_B64 must be set")
|
|
|
|
_IANA_SYNC_PASSWORD = os.environ.get("DATATRACKER_IANA_SYNC_PASSWORD", None)
|
|
if _IANA_SYNC_PASSWORD is not None:
|
|
IANA_SYNC_PASSWORD = _IANA_SYNC_PASSWORD
|
|
else:
|
|
raise RuntimeError("DATATRACKER_IANA_SYNC_PASSWORD must be set")
|
|
|
|
_RFC_EDITOR_SYNC_PASSWORD = os.environ.get("DATATRACKER_RFC_EDITOR_SYNC_PASSWORD", None)
|
|
if _RFC_EDITOR_SYNC_PASSWORD is not None:
|
|
RFC_EDITOR_SYNC_PASSWORD = os.environ.get("DATATRACKER_RFC_EDITOR_SYNC_PASSWORD")
|
|
else:
|
|
raise RuntimeError("DATATRACKER_RFC_EDITOR_SYNC_PASSWORD must be set")
|
|
|
|
_YOUTUBE_API_KEY = os.environ.get("DATATRACKER_YOUTUBE_API_KEY", None)
|
|
if _YOUTUBE_API_KEY is not None:
|
|
YOUTUBE_API_KEY = _YOUTUBE_API_KEY
|
|
else:
|
|
raise RuntimeError("DATATRACKER_YOUTUBE_API_KEY must be set")
|
|
|
|
_GITHUB_BACKUP_API_KEY = os.environ.get("DATATRACKER_GITHUB_BACKUP_API_KEY", None)
|
|
if _GITHUB_BACKUP_API_KEY is not None:
|
|
GITHUB_BACKUP_API_KEY = _GITHUB_BACKUP_API_KEY
|
|
else:
|
|
raise RuntimeError("DATATRACKER_GITHUB_BACKUP_API_KEY must be set")
|
|
|
|
_API_KEY_TYPE = os.environ.get("DATATRACKER_API_KEY_TYPE", None)
|
|
if _API_KEY_TYPE is not None:
|
|
API_KEY_TYPE = _API_KEY_TYPE
|
|
else:
|
|
raise RuntimeError("DATATRACKER_API_KEY_TYPE must be set")
|
|
|
|
_API_PUBLIC_KEY_PEM_B64 = os.environ.get("DATATRACKER_API_PUBLIC_KEY_PEM_B64", None)
|
|
if _API_PUBLIC_KEY_PEM_B64 is not None:
|
|
API_PUBLIC_KEY_PEM = b64decode(_API_PUBLIC_KEY_PEM_B64)
|
|
else:
|
|
raise RuntimeError("DATATRACKER_API_PUBLIC_KEY_PEM_B64 must be set")
|
|
|
|
_API_PRIVATE_KEY_PEM_B64 = os.environ.get("DATATRACKER_API_PRIVATE_KEY_PEM_B64", None)
|
|
if _API_PRIVATE_KEY_PEM_B64 is not None:
|
|
API_PRIVATE_KEY_PEM = b64decode(_API_PRIVATE_KEY_PEM_B64)
|
|
else:
|
|
raise RuntimeError("DATATRACKER_API_PRIVATE_KEY_PEM_B64 must be set")
|
|
|
|
# Set DEBUG if DATATRACKER_DEBUG env var is the word "true"
|
|
DEBUG = os.environ.get("DATATRACKER_DEBUG", "false").lower() == "true"
|
|
|
|
# DATATRACKER_ALLOWED_HOSTS env var is a newline-separated list of allowed hosts
|
|
_allowed_hosts_str = os.environ.get("DATATRACKER_ALLOWED_HOSTS", None)
|
|
if _allowed_hosts_str is not None:
|
|
ALLOWED_HOSTS = _multiline_to_list(_allowed_hosts_str)
|
|
|
|
DATABASES = {
|
|
"default": {
|
|
"HOST": os.environ.get("DATATRACKER_DB_HOST", "db"),
|
|
"PORT": os.environ.get("DATATRACKER_DB_PORT", "5432"),
|
|
"NAME": os.environ.get("DATATRACKER_DB_NAME", "datatracker"),
|
|
"ENGINE": "django.db.backends.postgresql",
|
|
"USER": os.environ.get("DATATRACKER_DB_USER", "django"),
|
|
"PASSWORD": os.environ.get("DATATRACKER_DB_PASS", ""),
|
|
"OPTIONS": json.loads(os.environ.get("DATATRACKER_DB_OPTS_JSON", "{}")),
|
|
},
|
|
}
|
|
|
|
# Configure persistent connections. A setting of 0 is Django's default.
|
|
_conn_max_age = os.environ.get("DATATRACKER_DB_CONN_MAX_AGE", "0")
|
|
# A string "none" means unlimited age.
|
|
DATABASES["default"]["CONN_MAX_AGE"] = (
|
|
None if _conn_max_age.lower() == "none" else int(_conn_max_age)
|
|
)
|
|
# Enable connection health checks if DATATRACKER_DB_CONN_HEALTH_CHECK is the string "true"
|
|
_conn_health_checks = bool(
|
|
os.environ.get("DATATRACKER_DB_CONN_HEALTH_CHECKS", "false").lower() == "true"
|
|
)
|
|
DATABASES["default"]["CONN_HEALTH_CHECKS"] = _conn_health_checks
|
|
|
|
# DATATRACKER_ADMINS is a newline-delimited list of addresses parseable by email.utils.parseaddr
|
|
_admins_str = os.environ.get("DATATRACKER_ADMINS", None)
|
|
if _admins_str is not None:
|
|
ADMINS = [parseaddr(admin) for admin in _multiline_to_list(_admins_str)]
|
|
else:
|
|
raise RuntimeError("DATATRACKER_ADMINS must be set")
|
|
|
|
USING_DEBUG_EMAIL_SERVER = (
|
|
os.environ.get("DATATRACKER_EMAIL_DEBUG", "false").lower() == "true"
|
|
)
|
|
EMAIL_HOST = os.environ.get("DATATRACKER_EMAIL_HOST", "localhost")
|
|
EMAIL_PORT = int(os.environ.get("DATATRACKER_EMAIL_PORT", "2025"))
|
|
|
|
_celery_password = os.environ.get("CELERY_PASSWORD", None)
|
|
if _celery_password is None:
|
|
raise RuntimeError("CELERY_PASSWORD must be set")
|
|
CELERY_BROKER_URL = "amqp://datatracker:{password}@{host}/{queue}".format(
|
|
host=os.environ.get("RABBITMQ_HOSTNAME", "dt-rabbitmq"),
|
|
password=_celery_password,
|
|
queue=os.environ.get("RABBITMQ_QUEUE", "dt"),
|
|
)
|
|
|
|
IANA_SYNC_USERNAME = "ietfsync"
|
|
IANA_SYNC_CHANGES_URL = "https://datatracker.iana.org:4443/data-tracker/changes"
|
|
IANA_SYNC_PROTOCOLS_URL = "http://www.iana.org/protocols/"
|
|
|
|
RFC_EDITOR_NOTIFICATION_URL = "http://www.rfc-editor.org/parser/parser.php"
|
|
|
|
_registration_api_key = os.environ.get("DATATRACKER_REGISTRATION_API_KEY", None)
|
|
if _registration_api_key is None:
|
|
raise RuntimeError("DATATRACKER_REGISTRATION_API_KEY must be set")
|
|
STATS_REGISTRATION_ATTENDEES_JSON_URL = f"https://registration.ietf.org/{{number}}/attendees/?apikey={_registration_api_key}"
|
|
|
|
# FIRST_CUTOFF_DAYS = 12
|
|
# SECOND_CUTOFF_DAYS = 12
|
|
# SUBMISSION_CUTOFF_DAYS = 26
|
|
# SUBMISSION_CORRECTION_DAYS = 57
|
|
MEETING_MATERIALS_SUBMISSION_CUTOFF_DAYS = 26
|
|
MEETING_MATERIALS_SUBMISSION_CORRECTION_DAYS = 54
|
|
|
|
# disable htpasswd by setting to a do-nothing command
|
|
HTPASSWD_COMMAND = "/bin/true"
|
|
|
|
_MEETECHO_CLIENT_ID = os.environ.get("DATATRACKER_MEETECHO_CLIENT_ID", None)
|
|
_MEETECHO_CLIENT_SECRET = os.environ.get("DATATRACKER_MEETECHO_CLIENT_SECRET", None)
|
|
if _MEETECHO_CLIENT_ID is not None and _MEETECHO_CLIENT_SECRET is not None:
|
|
MEETECHO_API_CONFIG = {
|
|
"api_base": os.environ.get(
|
|
"DATATRACKER_MEETECHO_API_BASE",
|
|
"https://meetings.conf.meetecho.com/api/v1/",
|
|
),
|
|
"client_id": _MEETECHO_CLIENT_ID,
|
|
"client_secret": _MEETECHO_CLIENT_SECRET,
|
|
"request_timeout": 3.01, # python-requests doc recommend slightly > a multiple of 3 seconds
|
|
}
|
|
else:
|
|
raise RuntimeError(
|
|
"DATATRACKER_MEETECHO_CLIENT_ID and DATATRACKER_MEETECHO_CLIENT_SECRET must be set"
|
|
)
|
|
|
|
# For APP_API_TOKENS, ccept either base64-encoded JSON or raw JSON, but not both
|
|
if "DATATRACKER_APP_API_TOKENS_JSON_B64" in os.environ:
|
|
if "DATATRACKER_APP_API_TOKENS_JSON" in os.environ:
|
|
raise RuntimeError(
|
|
"Only one of DATATRACKER_APP_API_TOKENS_JSON and DATATRACKER_APP_API_TOKENS_JSON_B64 may be set"
|
|
)
|
|
_APP_API_TOKENS_JSON = b64decode(
|
|
os.environ.get("DATATRACKER_APP_API_TOKENS_JSON_B64")
|
|
)
|
|
else:
|
|
_APP_API_TOKENS_JSON = os.environ.get("DATATRACKER_APP_API_TOKENS_JSON", None)
|
|
|
|
if _APP_API_TOKENS_JSON is not None:
|
|
APP_API_TOKENS = json.loads(_APP_API_TOKENS_JSON)
|
|
else:
|
|
APP_API_TOKENS = {}
|
|
|
|
EMAIL_COPY_TO = ""
|
|
|
|
# Until we teach the datatracker to look beyond cloudflare for this check
|
|
IDSUBMIT_MAX_DAILY_SAME_SUBMITTER = 5000
|
|
|
|
# Leave DATATRACKER_MATOMO_SITE_ID unset to disable Matomo reporting
|
|
if "DATATRACKER_MATOMO_SITE_ID" in os.environ:
|
|
MATOMO_DOMAIN_PATH = os.environ.get(
|
|
"DATATRACKER_MATOMO_DOMAIN_PATH", "analytics.ietf.org"
|
|
)
|
|
MATOMO_SITE_ID = os.environ.get("DATATRACKER_MATOMO_SITE_ID")
|
|
MATOMO_DISABLE_COOKIES = True
|
|
|
|
# Leave DATATRACKER_SCOUT_KEY unset to disable Scout APM agent
|
|
_SCOUT_KEY = os.environ.get("DATATRACKER_SCOUT_KEY", None)
|
|
if _SCOUT_KEY is not None:
|
|
if SERVER_MODE == "production":
|
|
PROD_PRE_APPS = [
|
|
"scout_apm.django",
|
|
]
|
|
else:
|
|
DEV_PRE_APPS = [
|
|
"scout_apm.django",
|
|
]
|
|
SCOUT_MONITOR = True
|
|
SCOUT_KEY = _SCOUT_KEY
|
|
SCOUT_NAME = os.environ.get("DATATRACKER_SCOUT_NAME", "Datatracker")
|
|
SCOUT_ERRORS_ENABLED = True
|
|
SCOUT_SHUTDOWN_MESSAGE_ENABLED = False
|
|
SCOUT_CORE_AGENT_SOCKET_PATH = "tcp://{host}:{port}".format(
|
|
host=os.environ.get("DATATRACKER_SCOUT_CORE_AGENT_HOST", "localhost"),
|
|
port=os.environ.get("DATATRACKER_SCOUT_CORE_AGENT_PORT", "6590"),
|
|
)
|
|
SCOUT_CORE_AGENT_DOWNLOAD = False
|
|
SCOUT_CORE_AGENT_LAUNCH = False
|
|
SCOUT_REVISION_SHA = __release_hash__[:7]
|
|
|
|
STATIC_URL = os.environ.get("DATATRACKER_STATIC_URL", None)
|
|
if STATIC_URL is None:
|
|
from ietf import __version__
|
|
|
|
STATIC_URL = f"https://static.ietf.org/dt/{__version__}/"
|
|
|
|
# Set these to the same as "production" in settings.py, whether production mode or not
|
|
MEDIA_ROOT = "/a/www/www6s/lib/dt/media/"
|
|
MEDIA_URL = "https://www.ietf.org/lib/dt/media/"
|
|
PHOTOS_DIRNAME = "photo"
|
|
PHOTOS_DIR = MEDIA_ROOT + PHOTOS_DIRNAME
|
|
|
|
# Normally only set for debug, but needed until we have a real FS
|
|
DJANGO_VITE_MANIFEST_PATH = os.path.join(BASE_DIR, "static/dist-neue/manifest.json")
|
|
|
|
# Binaries that are different in the docker image
|
|
DE_GFM_BINARY = "/usr/local/bin/de-gfm"
|
|
IDSUBMIT_IDNITS_BINARY = "/usr/local/bin/idnits"
|
|
|
|
# Duplicating production cache from settings.py and using it whether we're in production mode or not
|
|
MEMCACHED_HOST = os.environ.get("DT_MEMCACHED_SERVICE_HOST", "127.0.0.1")
|
|
MEMCACHED_PORT = os.environ.get("DT_MEMCACHED_SERVICE_PORT", "11211")
|
|
from ietf import __version__
|
|
|
|
CACHES = {
|
|
"default": {
|
|
"BACKEND": "ietf.utils.cache.LenientMemcacheCache",
|
|
"LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}",
|
|
"VERSION": __version__,
|
|
"KEY_PREFIX": "ietf:dt",
|
|
"KEY_FUNCTION": lambda key, key_prefix, version: (
|
|
f"{key_prefix}:{version}:{sha384(str(key).encode('utf8')).hexdigest()}"
|
|
),
|
|
},
|
|
"sessions": {
|
|
"BACKEND": "ietf.utils.cache.LenientMemcacheCache",
|
|
"LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}",
|
|
# No release-specific VERSION setting.
|
|
"KEY_PREFIX": "ietf:dt",
|
|
},
|
|
"htmlized": {
|
|
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
|
|
"LOCATION": "/a/cache/datatracker/htmlized",
|
|
"OPTIONS": {
|
|
"MAX_ENTRIES": 100000, # 100,000
|
|
},
|
|
},
|
|
"pdfized": {
|
|
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
|
|
"LOCATION": "/a/cache/datatracker/pdfized",
|
|
"OPTIONS": {
|
|
"MAX_ENTRIES": 100000, # 100,000
|
|
},
|
|
},
|
|
"slowpages": {
|
|
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
|
|
"LOCATION": "/a/cache/datatracker/slowpages",
|
|
"OPTIONS": {
|
|
"MAX_ENTRIES": 5000,
|
|
},
|
|
},
|
|
"celery-results": {
|
|
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
|
|
"LOCATION": f"{MEMCACHED_HOST}:{MEMCACHED_PORT}",
|
|
"KEY_PREFIX": "ietf:celery",
|
|
},
|
|
}
|
|
|
|
_csrf_trusted_origins_str = os.environ.get("DATATRACKER_CSRF_TRUSTED_ORIGINS")
|
|
if _csrf_trusted_origins_str is not None:
|
|
CSRF_TRUSTED_ORIGINS = _multiline_to_list(_csrf_trusted_origins_str)
|
|
|
|
# Console logs as JSON instead of plain when running in k8s
|
|
LOGGING["handlers"]["console"]["formatter"] = "json"
|
|
|
|
# Configure storages for the blob store
|
|
_blob_store_endpoint_url = os.environ.get("DATATRACKER_BLOB_STORE_ENDPOINT_URL")
|
|
_blob_store_access_key = os.environ.get("DATATRACKER_BLOB_STORE_ACCESS_KEY")
|
|
_blob_store_secret_key = os.environ.get("DATATRACKER_BLOB_STORE_SECRET_KEY")
|
|
if None in (_blob_store_endpoint_url, _blob_store_access_key, _blob_store_secret_key):
|
|
raise RuntimeError(
|
|
"All of DATATRACKER_BLOB_STORE_ENDPOINT_URL, DATATRACKER_BLOB_STORE_ACCESS_KEY, "
|
|
"and DATATRACKER_BLOB_STORE_SECRET_KEY must be set"
|
|
)
|
|
_blob_store_bucket_prefix = os.environ.get(
|
|
"DATATRACKER_BLOB_STORE_BUCKET_PREFIX", ""
|
|
)
|
|
_blob_store_enable_profiling = (
|
|
os.environ.get("DATATRACKER_BLOB_STORE_ENABLE_PROFILING", "false").lower() == "true"
|
|
)
|
|
_blob_store_max_attempts = int(
|
|
os.environ.get("DATATRACKER_BLOB_STORE_MAX_ATTEMPTS", BLOBSTORAGE_MAX_ATTEMPTS)
|
|
)
|
|
_blob_store_connect_timeout = float(
|
|
os.environ.get("DATATRACKER_BLOB_STORE_CONNECT_TIMEOUT", BLOBSTORAGE_CONNECT_TIMEOUT)
|
|
)
|
|
_blob_store_read_timeout = float(
|
|
os.environ.get("DATATRACKER_BLOB_STORE_READ_TIMEOUT", BLOBSTORAGE_READ_TIMEOUT)
|
|
)
|
|
for storage_name in MORE_STORAGE_NAMES:
|
|
STORAGES[storage_name] = {
|
|
"BACKEND": "ietf.doc.storage_backends.CustomS3Storage",
|
|
"OPTIONS": dict(
|
|
endpoint_url=_blob_store_endpoint_url,
|
|
access_key=_blob_store_access_key,
|
|
secret_key=_blob_store_secret_key,
|
|
security_token=None,
|
|
client_config=botocore.config.Config(
|
|
signature_version="s3v4",
|
|
connect_timeout=_blob_store_connect_timeout,
|
|
read_timeout=_blob_store_read_timeout,
|
|
retries={"total_max_attempts": _blob_store_max_attempts},
|
|
),
|
|
bucket_name=f"{_blob_store_bucket_prefix}{storage_name}".strip(),
|
|
ietf_log_blob_timing=_blob_store_enable_profiling,
|
|
),
|
|
}
|