Merge pull request #87 from cisagov/sspj/django-settings

Update Django settings
This commit is contained in:
Seamus Johnston 2022-08-31 16:59:04 +00:00 committed by GitHub
commit d88fa8fb3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 423 additions and 93 deletions

View file

@ -13,4 +13,5 @@ gunicorn = "*"
psycopg2-binary = "*"
[dev-packages]
django-debug-toolbar = "*"
django-debug-toolbar = "*"
nplusone = "*"

44
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "3c3bdeb59b98dcd8c01350c36619ab12b593f8c25846dc3425f481b587fc0ae8"
"sha256": "9a25a3e7f6574f0253c8f94b397b651448658dc9ac3ffb7faa9ac1f5e89d8dba"
},
"pipfile-spec": 6,
"requires": {},
@ -103,11 +103,11 @@
},
"marshmallow": {
"hashes": [
"sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb",
"sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"
"sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408",
"sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0"
],
"markers": "python_version >= '3.7'",
"version": "==3.17.0"
"version": "==3.17.1"
},
"orderedmultidict": {
"hashes": [
@ -204,11 +204,11 @@
},
"setuptools": {
"hashes": [
"sha256:7a2e7e95c3bf33f356b4c59aee7a6848585c4219dd3e941e43cc117888f210e4",
"sha256:c04a012ae3a1b2cc2aeed4893377b70ea61c6c143d0acceea16ec4b60de6e40d"
"sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
"sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"
],
"markers": "python_version >= '3.7'",
"version": "==65.0.1"
"version": "==65.3.0"
},
"six": {
"hashes": [
@ -236,6 +236,14 @@
"markers": "python_version >= '3.7'",
"version": "==3.5.2"
},
"blinker": {
"hashes": [
"sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36",
"sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.5"
},
"django": {
"hashes": [
"sha256:031ccb717782f6af83a0063a1957686e87cb4581ea61b47b3e9addf60687989a",
@ -246,11 +254,27 @@
},
"django-debug-toolbar": {
"hashes": [
"sha256:89a52128309eb4da12738801ff0c202d2ff8730d1c3225fac6acf630c303e661",
"sha256:97965f2630692de316ea0c1ca5bfa81660d7ba13146dbc6be2059cf55b35d0e5"
"sha256:95fc2fd29c56cc86678aae9f6919ececefe892f2a78c4004b193a223a8380c3d",
"sha256:fe7fe3f21865218827e2162ecc06eba386dfe8cffe4f3501c49bb4359e06a0e6"
],
"index": "pypi",
"version": "==3.5.0"
"version": "==3.6.0"
},
"nplusone": {
"hashes": [
"sha256:1726c0a10c0aa7eabb04e24db2882ff97b6b7ee29d729a8d97dcbd12ef5a5651",
"sha256:96b1e6e29e6af3e71b67d0cc012a5ec8c97c6a2f5399f4ba41a2bbe0e253a9ac"
],
"index": "pypi",
"version": "==1.0.0"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sqlparse": {
"hashes": [

View file

@ -1,8 +1,6 @@
"""
Django settings for .gov registrar project.
Generated by 'django-admin startproject' using Django 4.0.6.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
@ -19,10 +17,13 @@ $ docker-compose exec app python manage.py shell
"""
import environs
import os
from cfenv import AppEnv
from pathlib import Path
# # # ###
# Setup code goes here #
# # # ###
env = environs.Env()
# Get secrets from Cloud.gov user provided service, if exists
@ -33,56 +34,117 @@ if key_service and key_service.credentials:
else:
secret = env
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# # # ###
# Values obtained externally #
# # # ###
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = secret("DJANGO_SECRET_KEY")
path = Path(__file__)
env_db_url = env.dj_db_url("DATABASE_URL")
env_debug = env.bool("DJANGO_DEBUG", default=False)
env_log_level = env.str("DJANGO_LOG_LEVEL", "DEBUG")
secret_key = secret("DJANGO_SECRET_KEY")
# region: Basic Django Config-----------------------------------------------###
# Build paths inside the project like this: BASE_DIR / "subdir".
BASE_DIR = path.resolve().parent.parent
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool("DJANGO_DEBUG", default=False)
# TODO: configure and document security settings
ALLOWED_HOSTS = ["getgov-unstable.app.cloud.gov", "get.gov"]
ALLOWED_CIDR_NETS = ["10.0.0.0/8"] # nosec
USE_X_FORWARDED_HOST = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
CORS_ALLOW_ALL_ORIGINS = False
DEBUG = env_debug
# TODO: are all of these needed? Need others?
# Applications are modular pieces of code.
# They are provided by Django, by third-parties, or by yourself.
# Installing them here makes them available for execution.
# Do not access INSTALLED_APPS directly. Use `django.apps.apps` instead.
INSTALLED_APPS = [
# Django automatic admin interface reads metadata
# from database models to provide a quick, model-centric
# interface where trusted users can manage content
"django.contrib.admin",
# vv Required by django.contrib.admin vv
# the "user" model! *\o/*
"django.contrib.auth",
# generic interface for Django models
"django.contrib.contenttypes",
# required for CSRF protection and many other things
"django.contrib.sessions",
# framework for displaying messages to the user
"django.contrib.messages",
# ^^ Required by django.contrib.admin ^^
# collects static files from each of your applications
# (and any other places you specify) into a single location
# that can easily be served in production
"django.contrib.staticfiles",
]
# TODO: document these for future maintainers
# Middleware are routines for processing web requests.
# Adding them here turns them "on"; Django will perform the
# specified routines on each incoming request and outgoing response.
MIDDLEWARE = [
# django-allow-cidr: enable use of CIDR IP ranges in ALLOWED_HOSTS
"allow_cidr.middleware.AllowCIDRMiddleware",
# provide security enhancements to the request/response cycle
"django.middleware.security.SecurityMiddleware",
# store and retrieve arbitrary data on a per-site-visitor basis
"django.contrib.sessions.middleware.SessionMiddleware",
# add a few conveniences for perfectionists, see documentation
"django.middleware.common.CommonMiddleware",
# add protection against Cross Site Request Forgeries by adding
# hidden form fields to POST forms and checking requests for the correct value
"django.middleware.csrf.CsrfViewMiddleware",
# add `user` (the currently-logged-in user) to incoming HttpRequest objects
"django.contrib.auth.middleware.AuthenticationMiddleware",
# provide framework for displaying messages to the user, see documentation
"django.contrib.messages.middleware.MessageMiddleware",
# provide clickjacking protection via the X-Frame-Options header
"django.middleware.clickjacking.XFrameOptionsMiddleware",
# django-csp: enable use of Content-Security-Policy header
"csp.middleware.CSPMiddleware",
]
# application object used by Djangos built-in servers (e.g. `runserver`)
WSGI_APPLICATION = "registrar.config.wsgi.application"
# endregion
# region: Assets and HTML and Caching---------------------------------------###
# https://docs.djangoproject.com/en/4.0/howto/static-files/
# Caching is disabled by default.
# For a low to medium traffic site, caching causes more
# problems than it solves. Should caching be desired,
# a reasonable start might be:
# CACHES = {
# "default": {
# "BACKEND": "django.core.cache.backends.db.DatabaseCache",
# }
# }
# Absolute path to the directory where `collectstatic`
# will place static files for deployment.
# Do not use this directory for permanent storage -
# it is for Django!
STATIC_ROOT = BASE_DIR / "static"
# TODO: decide on template engine and document in ADR
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"],
# look for templates inside installed apps
# required by django-debug-toolbar
"APP_DIRS": True,
"OPTIONS": {
# IMPORTANT security setting: escapes HTMLEntities,
# helping to prevent XSS attacks
"autoescape": True,
# context processors are callables which return
# dicts - Django merges them into the context
# dictionary used to render the templates
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
@ -93,15 +155,117 @@ TEMPLATES = [
},
]
# endregion
# region: Database----------------------------------------------------------###
# Wrap each view in a transaction on the database
# A decorator can be used for views which have no database activity:
# from django.db import transaction
# @transaction.non_atomic_requests
env_db_url["ATOMIC_REQUESTS"] = True
DATABASES = {
# dj-database-url package takes the supplied Postgres connection string
# and converts it into a dictionary with the correct USER, HOST, etc
"default": env_db_url,
}
# Specify default field type to use for primary keys
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# endregion
# region: Email-------------------------------------------------------------###
# email address to use for various automated correspondence
# TODO: pick something sensible here
DEFAULT_FROM_EMAIL = "registrar@get.gov"
# connect to an (external) SMTP server for sending email
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
# TODO: configure these when the values are known
# EMAIL_HOST = ""
# EMAIL_HOST_PASSWORD = ""
# EMAIL_HOST_USER = ""
# EMAIL_PORT = 587
# for mail sent with mail_admins or mail_managers
EMAIL_SUBJECT_PREFIX = "[Attn: .gov admin] "
# use a TLS (secure) connection when talking to the SMTP server
# TLS generally uses port 587
EMAIL_USE_TLS = True
# mutually exclusive with EMAIL_USE_TLS = True
# SSL generally uses port 465
EMAIL_USE_SSL = False
# timeout in seconds for blocking operations, like the connection attempt
EMAIL_TIMEOUT = 30
# email address to use for sending error reports
SERVER_EMAIL = "root@get.gov"
# endregion
# region: Headers-----------------------------------------------------------###
# Content-Length header is set by django.middleware.common.CommonMiddleware
# X-Frame-Options header is set by
# django.middleware.clickjacking.XFrameOptionsMiddleware
# and configured in the Security and Privacy section of this file.
# Strict-Transport-Security is set by django.middleware.security.SecurityMiddleware
# and configured in the Security and Privacy section of this file.
# prefer contents of X-Forwarded-Host header to Host header
# as Host header may contain a proxy rather than the actual client
USE_X_FORWARDED_HOST = True
# endregion
# region: Internationalisation----------------------------------------------###
# https://docs.djangoproject.com/en/4.0/topics/i18n/
# Charset to use for HttpResponse objects; used in Content-Type header
DEFAULT_CHARSET = "utf-8"
# provide fallback language if translation file is missing or
# user's locale is not supported - requires USE_I18N = True
LANGUAGE_CODE = "en-us"
# allows language cookie to be sent if the user
# is coming to our site from an external page.
LANGUAGE_COOKIE_SAMESITE = None
# only send via HTTPS connection
LANGUAGE_COOKIE_SECURE = True
# to display datetimes in templates
# and to interpret datetimes entered in forms
TIME_ZONE = "UTC"
# enable Djangos translation system
USE_I18N = True
# enable localized formatting of numbers and dates
USE_L10N = True
# make datetimes timezone-aware by default
USE_TZ = True
# endregion
# region: Logging-----------------------------------------------------------###
# No file logger is configured, because containerized apps
# do not log to the file system.
# TODO: Configure better logging options
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] "
"%(message)s", # noqa
"%(message)s",
"datefmt": "%d/%b/%Y %H:%M:%S",
},
"simple": {
@ -110,7 +274,7 @@ LOGGING = {
},
"handlers": {
"console": {
"level": "DEBUG" if DEBUG else "INFO",
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "verbose",
},
@ -119,7 +283,7 @@ LOGGING = {
"django": {
"handlers": ["console"],
"propagate": True,
"level": os.getenv("DJANGO_LOG_LEVEL", "DEBUG"),
"level": env_log_level,
},
"django.template": {
"handlers": ["console"],
@ -134,67 +298,208 @@ LOGGING = {
},
}
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
"default": env.dj_db_url("DATABASE_URL"),
}
# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
USE_L10N = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_ROOT = BASE_DIR / "static"
STATIC_URL = "static/"
ADMIN_URL = "admin/"
ROOT_URLCONF = "registrar.config.urls"
WSGI_APPLICATION = "registrar.config.wsgi.application"
# TODO: FAC example for REST framework
API_VERSION = "0"
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.BasicAuthentication",
"users.auth.ExpiringTokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"TEST_REQUEST_RENDERER_CLASSES": [
"rest_framework.renderers.MultiPartRenderer",
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.TemplateHTMLRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
],
"TEST_REQUEST_DEFAULT_FORMAT": "api",
}
# endregion
# region: Login-------------------------------------------------------------###
# TODO: FAC example for login.gov
SIMPLE_JWT = {
"ALGORITHM": "RS256",
"AUDIENCE": None,
"ISSUER": "https://idp.int.identitysandbox.gov/",
"JWK_URL": "https://idp.int.identitysandbox.gov/api/openid_connect/certs",
"LEEWAY": 0,
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.UntypedToken",),
"USER_ID_CLAIM": "sub",
}
TOKEN_AUTH = {"TOKEN_TTL": 3600}
# SIMPLE_JWT = {
# "ALGORITHM": "RS256",
# "AUDIENCE": None,
# "ISSUER": "https://idp.int.identitysandbox.gov/",
# "JWK_URL": "https://idp.int.identitysandbox.gov/api/openid_connect/certs",
# "LEEWAY": 0,
# "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.UntypedToken",),
# "USER_ID_CLAIM": "sub",
# }
# TOKEN_AUTH = {"TOKEN_TTL": 3600}
# endregion
# region: Rest Framework/API------------------------------------------------###
# Enable CORS if api is served at subdomain
# https://github.com/adamchainz/django-cors-headers
# TODO: FAC example for REST framework
# API_VERSION = "0"
# REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": [
# "rest_framework.authentication.BasicAuthentication",
# "users.auth.ExpiringTokenAuthentication",
# ],
# "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
# "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
# "PAGE_SIZE": 10,
# "TEST_REQUEST_RENDERER_CLASSES": [
# "rest_framework.renderers.MultiPartRenderer",
# "rest_framework.renderers.JSONRenderer",
# "rest_framework.renderers.TemplateHTMLRenderer",
# "rest_framework.renderers.BrowsableAPIRenderer",
# ],
# "TEST_REQUEST_DEFAULT_FORMAT": "api",
# }
# endregion
# region: Routing-----------------------------------------------------------###
# ~ Set by django.middleware.common.CommonMiddleware
# APPEND_SLASH = True
# PREPEND_WWW = False
# full Python import path to the root URLconf
ROOT_URLCONF = "registrar.config.urls"
# URL to use when referring to static files located in STATIC_ROOT
# Must be relative and end with "/"
STATIC_URL = "public/"
# endregion
# region: Security and Privacy----------------------------------------------###
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = secret_key
# Use this variable for doing SECRET_KEY rotation, see documentation
SECRET_KEY_FALLBACKS: "list[str]" = []
# ~ Set by django.middleware.security.SecurityMiddleware
# SECURE_CONTENT_TYPE_NOSNIFF = True
# SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"
# SECURE_REDIRECT_EXEMPT = []
# SECURE_REFERRER_POLICY = "same-origin"
# SECURE_SSL_HOST = None
# ~ Overridden from django.middleware.security.SecurityMiddleware
# adds the includeSubDomains directive to the HTTP Strict Transport Security header
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
# adds the preload directive to the HTTP Strict Transport Security header
SECURE_HSTS_PRELOAD = True
# TODO: set this value to 31536000 (1 year) for production
SECURE_HSTS_SECONDS = 300
# redirect all non-HTTPS requests to HTTPS
SECURE_SSL_REDIRECT = True
# ~ Set by django.middleware.common.CommonMiddleware
# DISALLOWED_USER_AGENTS = []
# The host/domain names that Django can serve.
# This is a security measure to prevent HTTP Host header attacks,
# which are possible even under many seemingly-safe
# web server configurations.
ALLOWED_HOSTS = [
"getgov-unstable.app.cloud.gov",
"get.gov",
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
# Extend ALLOWED_HOSTS.
# IP addresses can also be hosts, which are used by internal
# load balancers for health checks, etc.
ALLOWED_CIDR_NETS = ["10.0.0.0/8"]
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# ~ Below are some protections from cross-site request forgery.
# This is canonically done by including a nonce value
# in pages sent to the user, which the user is expected
# to send back. The specifics of implementation are
# intricate and varied.
# Store the token server-side, do not send it
# to the user via a cookie. This means each page
# which requires protection must place the token
# in the HTML explicitly, otherwise the user will
# get a 403 error when they submit.
CSRF_USE_SESSIONS = True
# Expiry of CSRF cookie, in seconds.
# None means "use session-based CSRF cookies".
CSRF_COOKIE_AGE = None
# Prevent JavaScript from reading the CSRF cookie.
# Has no effect with CSRF_USE_SESSIONS = True.
CSRF_COOKIE_HTTPONLY = True
# Only send the cookie via HTTPS connections.
# Has no effect with CSRF_USE_SESSIONS = True.
CSRF_COOKIE_SECURE = True
# Protect from non-targeted attacks by obscuring
# the CSRF cookie name from the default.
# Has no effect with CSRF_USE_SESSIONS = True.
CSRF_COOKIE_NAME = "CrSiReFo"
# Prevents CSRF cookie from being sent if the user
# is coming to our site from an external page.
# Has no effect with CSRF_USE_SESSIONS = True.
CSRF_COOKIE_SAMESITE = "Strict"
# Change header name to match cookie name.
# Has no effect with CSRF_USE_SESSIONS = True.
CSRF_HEADER_NAME = "HTTP_X_CRSIREFO"
# Max parameters that may be received via GET or POST
# TODO: 1000 is the default, may need to tune upward for
# large DNS zone files, if records are represented by
# individual form fields.
DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000
# age of session cookies, in seconds (28800 = 8 hours)
SESSION_COOKIE_AGE = 28800
# instruct the browser to forbid client-side JavaScript
# from accessing the cookie
SESSION_COOKIE_HTTPONLY = True
# are we a spring boot application? who knows!
SESSION_COOKIE_NAME = "JSESSIONID"
# Prevents session cookie from being sent if the user
# is coming to our site from an external page.
SESSION_COOKIE_SAMESITE = "Strict"
# instruct browser to only send cookie via HTTPS
SESSION_COOKIE_SECURE = True
# ~ Set by django.middleware.clickjacking.XFrameOptionsMiddleware
# prevent clickjacking by instructing the browser not to load
# our site within an iframe
# X_FRAME_OPTIONS = "Deny"
# endregion
# region: Testing-----------------------------------------------------------###
# Additional directories searched for fixture files.
# The fixtures directory of each application is searched by default.
# Must use unix style "/" path separators.
FIXTURE_DIRS: "list[str]" = []
# endregion
# # # ###
# Development settings #
# # # ###
if DEBUG:
# used by debug() context processor
INTERNAL_IPS = [
"127.0.0.1",
"::1",
]
# allow dev laptop to connect
ALLOWED_HOSTS += ("localhost",)
SECURE_SSL_REDIRECT = False
SECURE_HSTS_PRELOAD = False
# discover potentially inefficient database queries
# TODO: use settings overrides to ensure this always is True during tests
INSTALLED_APPS += ("nplusone.ext.django",)
MIDDLEWARE += ("nplusone.ext.django.NPlusOneMiddleware",)
NPLUSONE_RAISE = True
# insert the amazing django-debug-toolbar
INSTALLED_APPS += ("debug_toolbar",)
MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")
DEBUG_TOOLBAR_CONFIG = {
# due to Docker, bypass Debug Toolbar's check on INTERNAL_IPS
"SHOW_TOOLBAR_CALLBACK": lambda _: True,
}