Files
CTFd/CTFd/config.py
Kevin Chung a1e4f15bdc Make HTML sanitization an optional setting (#1556)
* Add new `HTML_SANITIZATION` server config to make HTML sanitization optional
2020-07-19 21:56:53 -04:00

380 lines
14 KiB
Python

import configparser
import os
from distutils.util import strtobool
def process_boolean_str(value):
if type(value) is bool:
return value
if value is None:
return False
if value == "":
return None
return bool(strtobool(value))
def empty_str_cast(value, default=None):
if value == "":
return default
return value
def gen_secret_key():
# Attempt to read the secret from the secret file
# This will fail if the secret has not been written
try:
with open(".ctfd_secret_key", "rb") as secret:
key = secret.read()
except (OSError, IOError):
key = None
if not key:
key = os.urandom(64)
# Attempt to write the secret file
# This will fail if the filesystem is read-only
try:
with open(".ctfd_secret_key", "wb") as secret:
secret.write(key)
secret.flush()
except (OSError, IOError):
pass
return key
config_ini = configparser.ConfigParser()
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.ini")
config_ini.read(path)
# fmt: off
class Config(object):
"""
CTFd Configuration Object
"""
"""
=== REQUIRED SETTINGS ===
SECRET_KEY:
The secret value used to creation sessions and sign strings. This should be set to a random string. In the
interest of ease, CTFd will automatically create a secret key file for you. If you wish to add this secret key
to your instance you should hard code this value to a random static value.
You can also remove .ctfd_secret_key from the .gitignore file and commit this file into whatever repository
you are using.
http://flask.pocoo.org/docs/latest/quickstart/#sessions
DATABASE_URL:
The URI that specifies the username, password, hostname, port, and database of the server
used to hold the CTFd database.
e.g. mysql+pymysql://root:<YOUR_PASSWORD_HERE>@localhost/ctfd
CACHE_TYPE:
Specifies how CTFd should cache configuration values. If CACHE_TYPE is set to 'redis', CTFd will make use
of the REDIS_URL specified in environment variables. You can also choose to hardcode the REDIS_URL here.
It is important that you specify some sort of cache as CTFd uses it to store values received from the database. If
no cache is specified, CTFd will default to a simple per-worker cache. The simple cache cannot be effectively used
with multiple workers.
REDIS_URL is the URL to connect to a Redis server.
e.g. redis://user:password@localhost:6379
http://pythonhosted.org/Flask-Caching/#configuring-flask-caching
"""
SECRET_KEY: str = os.getenv("SECRET_KEY") \
or empty_str_cast(config_ini["server"]["SECRET_KEY"]) \
or gen_secret_key()
DATABASE_URL: str = os.getenv("DATABASE_URL") \
or empty_str_cast(config_ini["server"]["DATABASE_URL"]) \
or f"sqlite:///{os.path.dirname(os.path.abspath(__file__))}/ctfd.db"
REDIS_URL: str = os.getenv("REDIS_URL") \
or empty_str_cast(config_ini["server"]["REDIS_URL"])
SQLALCHEMY_DATABASE_URI = DATABASE_URL
CACHE_REDIS_URL = REDIS_URL
if CACHE_REDIS_URL:
CACHE_TYPE: str = "redis"
else:
CACHE_TYPE: str = "filesystem"
CACHE_DIR: str = os.path.join(
os.path.dirname(__file__), os.pardir, ".data", "filesystem_cache"
)
# Override the threshold of cached values on the filesystem. The default is 500. Don't change unless you know what you're doing.
CACHE_THRESHOLD: int = 0
"""
=== SECURITY ===
SESSION_COOKIE_HTTPONLY:
Controls if cookies should be set with the HttpOnly flag. Defaults to True
PERMANENT_SESSION_LIFETIME:
The lifetime of a session. The default is 604800 seconds (7 days).
TRUSTED_PROXIES:
Defines a set of regular expressions used for finding a user's IP address if the CTFd instance
is behind a proxy. If you are running a CTF and users are on the same network as you, you may choose to remove
some proxies from the list.
CTFd only uses IP addresses for cursory tracking purposes. It is ill-advised to do anything complicated based
solely on IP addresses unless you know what you are doing.
"""
SESSION_COOKIE_HTTPONLY: bool = process_boolean_str(os.getenv("SESSION_COOKIE_HTTPONLY")) \
or config_ini["security"].getboolean("SESSION_COOKIE_HTTPONLY") \
or True
SESSION_COOKIE_SAMESITE: str = os.getenv("SESSION_COOKIE_SAMESITE") \
or empty_str_cast(config_ini["security"]["SESSION_COOKIE_SAMESITE"]) \
or "Lax"
PERMANENT_SESSION_LIFETIME: int = int(os.getenv("PERMANENT_SESSION_LIFETIME", 0)) \
or config_ini["security"].getint("PERMANENT_SESSION_LIFETIME") \
or 604800
TRUSTED_PROXIES = [
r"^127\.0\.0\.1$",
# Remove the following proxies if you do not trust the local network
# For example if you are running a CTF on your laptop and the teams are
# all on the same network
r"^::1$",
r"^fc00:",
r"^10\.",
r"^172\.(1[6-9]|2[0-9]|3[0-1])\.",
r"^192\.168\.",
]
"""
=== EMAIL ===
MAILFROM_ADDR:
The email address that emails are sent from if not overridden in the configuration panel.
MAIL_SERVER:
The mail server that emails are sent from if not overriden in the configuration panel.
MAIL_PORT:
The mail port that emails are sent from if not overriden in the configuration panel.
MAIL_USEAUTH
Whether or not to use username and password to authenticate to the SMTP server
MAIL_USERNAME
The username used to authenticate to the SMTP server if MAIL_USEAUTH is defined
MAIL_PASSWORD
The password used to authenticate to the SMTP server if MAIL_USEAUTH is defined
MAIL_TLS
Whether to connect to the SMTP server over TLS
MAIL_SSL
Whether to connect to the SMTP server over SSL
MAILGUN_API_KEY
Mailgun API key to send email over Mailgun. As of CTFd v3, Mailgun integration is deprecated.
Installations using the Mailgun API should migrate over to SMTP settings.
MAILGUN_BASE_URL
Mailgun base url to send email over Mailgun. As of CTFd v3, Mailgun integration is deprecated.
Installations using the Mailgun API should migrate over to SMTP settings.
"""
MAILFROM_ADDR: str = os.getenv("MAILFROM_ADDR") \
or config_ini["email"]["MAILFROM_ADDR"] \
or "noreply@ctfd.io"
MAIL_SERVER: str = os.getenv("MAIL_SERVER") \
or empty_str_cast(config_ini["email"]["MAIL_SERVER"])
MAIL_PORT: str = os.getenv("MAIL_PORT") \
or empty_str_cast(config_ini["email"]["MAIL_PORT"])
MAIL_USEAUTH: bool = process_boolean_str(os.getenv("MAIL_USEAUTH")) \
or process_boolean_str(config_ini["email"]["MAIL_USEAUTH"])
MAIL_USERNAME: str = os.getenv("MAIL_USERNAME") \
or empty_str_cast(config_ini["email"]["MAIL_USERNAME"])
MAIL_PASSWORD: str = os.getenv("MAIL_PASSWORD") \
or empty_str_cast(config_ini["email"]["MAIL_PASSWORD"])
MAIL_TLS: bool = process_boolean_str(os.getenv("MAIL_TLS")) \
or process_boolean_str(config_ini["email"]["MAIL_TLS"])
MAIL_SSL: bool = process_boolean_str(os.getenv("MAIL_SSL")) \
or process_boolean_str(config_ini["email"]["MAIL_SSL"])
MAILGUN_API_KEY: str = os.getenv("MAILGUN_API_KEY") \
or empty_str_cast(config_ini["email"]["MAILGUN_API_KEY"])
MAILGUN_BASE_URL: str = os.getenv("MAILGUN_BASE_URL") \
or empty_str_cast(config_ini["email"]["MAILGUN_API_KEY"])
"""
=== LOGS ===
LOG_FOLDER:
The location where logs are written. These are the logs for CTFd key submissions, registrations, and logins.
The default location is the CTFd/logs folder.
"""
LOG_FOLDER: str = os.getenv("LOG_FOLDER") \
or empty_str_cast(config_ini["logs"]["LOG_FOLDER"]) \
or os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
"""
=== UPLOADS ===
UPLOAD_PROVIDER:
Specifies the service that CTFd should use to store files.
UPLOAD_FOLDER:
The location where files are uploaded. The default destination is the CTFd/uploads folder.
AWS_ACCESS_KEY_ID:
AWS access token used to authenticate to the S3 bucket.
AWS_SECRET_ACCESS_KEY:
AWS secret token used to authenticate to the S3 bucket.
AWS_S3_BUCKET:
The unique identifier for your S3 bucket.
AWS_S3_ENDPOINT_URL:
A URL pointing to a custom S3 implementation.
"""
UPLOAD_PROVIDER: str = os.getenv("UPLOAD_PROVIDER") \
or empty_str_cast(config_ini["uploads"]["UPLOAD_PROVIDER"]) \
or "filesystem"
UPLOAD_FOLDER: str = os.getenv("UPLOAD_FOLDER") \
or empty_str_cast(config_ini["uploads"]["UPLOAD_FOLDER"]) \
or os.path.join(os.path.dirname(os.path.abspath(__file__)), "uploads")
if UPLOAD_PROVIDER == "s3":
AWS_ACCESS_KEY_ID: str = os.getenv("AWS_ACCESS_KEY_ID") \
or empty_str_cast(config_ini["uploads"]["AWS_ACCESS_KEY_ID"])
AWS_SECRET_ACCESS_KEY: str = os.getenv("AWS_SECRET_ACCESS_KEY") \
or empty_str_cast(config_ini["uploads"]["AWS_SECRET_ACCESS_KEY"])
AWS_S3_BUCKET: str = os.getenv("AWS_S3_BUCKET") \
or empty_str_cast(config_ini["uploads"]["AWS_S3_BUCKET"])
AWS_S3_ENDPOINT_URL: str = os.getenv("AWS_S3_ENDPOINT_URL") \
or empty_str_cast(config_ini["uploads"]["AWS_S3_ENDPOINT_URL"])
"""
=== OPTIONAL ===
REVERSE_PROXY:
Specifies whether CTFd is behind a reverse proxy or not. Set to True if using a reverse proxy like nginx.
You can also specify a comma seperated set of numbers specifying the reverse proxy configuration settings.
See https://werkzeug.palletsprojects.com/en/0.15.x/middleware/proxy_fix/#werkzeug.middleware.proxy_fix.ProxyFix.
For example to configure `x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1` specify `1,1,1,1,1`.
Alternatively if you specify `true` CTFd will default to the above behavior with all proxy settings set to 1.
TEMPLATES_AUTO_RELOAD:
Specifies whether Flask should check for modifications to templates and reload them automatically. Defaults True.
SQLALCHEMY_TRACK_MODIFICATIONS:
Automatically disabled to suppress warnings and save memory. You should only enable this if you need it. Defaults False.
SWAGGER_UI:
Enable the Swagger UI endpoint at /api/v1/
UPDATE_CHECK:
Specifies whether or not CTFd will check whether or not there is a new version of CTFd. Defaults True.
APPLICATION_ROOT:
Specifies what path CTFd is mounted under. It can be used to run CTFd in a subdirectory.
Example: /ctfd
HTML_SANITIZATION:
Specifies whether CTFd should sanitize HTML content from pages and descriptions
SERVER_SENT_EVENTS:
Specifies whether or not to enable to server-sent events based Notifications system.
SQLALCHEMY_ENGINE_OPTIONS:
A dictionary of keyword args to send to the underlying SQLAlchemy create_engine() call.
https://docs.sqlalchemy.org/en/13/core/engines.html#sqlalchemy.create_engine
https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/#configuration-keys
"""
REVERSE_PROXY: bool = process_boolean_str(os.getenv("REVERSE_PROXY")) \
or empty_str_cast(config_ini["optional"]["REVERSE_PROXY"]) \
or False
TEMPLATES_AUTO_RELOAD: bool = process_boolean_str(os.getenv("TEMPLATES_AUTO_RELOAD")) \
or empty_str_cast(config_ini["optional"]["TEMPLATES_AUTO_RELOAD"]) \
or True
SQLALCHEMY_TRACK_MODIFICATIONS: bool = process_boolean_str(os.getenv("SQLALCHEMY_TRACK_MODIFICATIONS")) \
or empty_str_cast(config_ini["optional"]["SQLALCHEMY_TRACK_MODIFICATIONS"]) \
or False
SWAGGER_UI: bool = os.getenv("SWAGGER_UI") \
or empty_str_cast(config_ini["optional"]["SWAGGER_UI"]) \
or False
SWAGGER_UI_ENDPOINT: str = "/" if SWAGGER_UI else None
UPDATE_CHECK: bool = process_boolean_str(os.getenv("UPDATE_CHECK")) \
or empty_str_cast(config_ini["optional"]["UPDATE_CHECK"]) \
or True
APPLICATION_ROOT: str = os.getenv("APPLICATION_ROOT") \
or empty_str_cast(config_ini["optional"]["APPLICATION_ROOT"]) \
or "/"
SERVER_SENT_EVENTS: bool = process_boolean_str(os.getenv("SERVER_SENT_EVENTS")) \
or empty_str_cast(config_ini["optional"]["SERVER_SENT_EVENTS"]) \
or True
HTML_SANITIZATION: bool = process_boolean_str(os.getenv("HTML_SANITIZATION")) \
or empty_str_cast(config_ini["optional"]["HTML_SANITIZATION"]) \
or False
if DATABASE_URL.startswith("sqlite") is False:
SQLALCHEMY_ENGINE_OPTIONS = {
"max_overflow": int(os.getenv("SQLALCHEMY_MAX_OVERFLOW", 0))
or int(empty_str_cast(config_ini["optional"]["SQLALCHEMY_MAX_OVERFLOW"], default=0)) # noqa: E131
or 20, # noqa: E131
"pool_pre_ping": process_boolean_str(os.getenv("SQLALCHEMY_POOL_PRE_PING"))
or empty_str_cast(config_ini["optional"]["SQLALCHEMY_POOL_PRE_PING"]) # noqa: E131
or True, # noqa: E131
}
"""
=== OAUTH ===
MajorLeagueCyber Integration
Register an event at https://majorleaguecyber.org/ and use the Client ID and Client Secret here
"""
OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID") \
or empty_str_cast(config_ini["oauth"]["OAUTH_CLIENT_ID"])
OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET") \
or empty_str_cast(config_ini["oauth"]["OAUTH_CLIENT_SECRET"])
# fmt: on
class TestingConfig(Config):
SECRET_KEY = "AAAAAAAAAAAAAAAAAAAA"
PRESERVE_CONTEXT_ON_EXCEPTION = False
TESTING = True
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.getenv("TESTING_DATABASE_URL") or "sqlite://"
SERVER_NAME = "localhost"
UPDATE_CHECK = False
REDIS_URL = None
CACHE_TYPE = "simple"
CACHE_THRESHOLD = 500
SAFE_MODE = True