From 695a07daf4837a9b49888fbc0bd76d063e500dcd Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 22 Apr 2019 16:33:28 -0400 Subject: [PATCH] Clean up settings.py and restrict import of LDAP parameters --- netbox/netbox/settings.py | 352 ++++++++++++++++++++++++-------------- 1 file changed, 224 insertions(+), 128 deletions(-) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 5f8ac3f2b..662fb1f67 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -1,19 +1,37 @@ import logging import os +import platform import socket -import sys import warnings from django.contrib.messages import constants as messages from django.core.exceptions import ImproperlyConfigured -# Django 2.1 requires Python 3.5+ -if sys.version_info < (3, 5): + +# +# Environment setup +# + +VERSION = '2.6.0-dev' + +# Hostname +HOSTNAME = platform.node() + +# Set the base directory two levels up +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Django 2.1+ requires Python 3.5+ +if platform.python_version_tuple() < ('3', '5'): raise RuntimeError( - "NetBox requires Python 3.5 or higher (current: Python {})".format(sys.version.split()[0]) + "NetBox requires Python 3.5 or higher (current: Python {})".format(platform.python_version()) ) -# Check for configuration file + +# +# Configuration import +# + +# Import configuration parameters try: from netbox import configuration except ImportError: @@ -21,22 +39,20 @@ except ImportError: "Configuration file is not present. Please define netbox/netbox/configuration.py per the documentation." ) - -VERSION = '2.6.0-dev' - -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -# Import required configuration parameters -ALLOWED_HOSTS = DATABASE = SECRET_KEY = None -for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY', 'REDIS']: - try: - globals()[setting] = getattr(configuration, setting) - except AttributeError: +# Enforce required configuration parameters +for parameter in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY', 'REDIS']: + if not hasattr(configuration, parameter): raise ImproperlyConfigured( - "Mandatory setting {} is missing from configuration.py.".format(setting) + "Required parameter {} is missing from configuration.py.".format(parameter) ) -# Import optional configuration parameters +# Set required parameters +ALLOWED_HOSTS = getattr(configuration, 'ALLOWED_HOSTS') +DATABASE = getattr(configuration, 'DATABASE') +REDIS = getattr(configuration, 'REDIS') +SECRET_KEY = getattr(configuration, 'SECRET_KEY') + +# Set optional parameters ADMINS = getattr(configuration, 'ADMINS', []) BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', '') BANNER_LOGIN = getattr(configuration, 'BANNER_LOGIN', '') @@ -61,14 +77,13 @@ LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None) MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False) MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000) MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/') -NAPALM_USERNAME = getattr(configuration, 'NAPALM_USERNAME', '') +NAPALM_ARGS = getattr(configuration, 'NAPALM_ARGS', {}) NAPALM_PASSWORD = getattr(configuration, 'NAPALM_PASSWORD', '') NAPALM_TIMEOUT = getattr(configuration, 'NAPALM_TIMEOUT', 30) -NAPALM_ARGS = getattr(configuration, 'NAPALM_ARGS', {}) +NAPALM_USERNAME = getattr(configuration, 'NAPALM_USERNAME', '') PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50) PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False) REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/') -REDIS = getattr(configuration, 'REDIS', {}) SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None) SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d') SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i') @@ -77,61 +92,25 @@ TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a') TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC') WEBHOOKS_ENABLED = getattr(configuration, 'WEBHOOKS_ENABLED', False) -CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS - -# Attempt to import LDAP configuration if it has been defined -LDAP_IGNORE_CERT_ERRORS = False -try: - from netbox.ldap_config import * - LDAP_CONFIGURED = True -except ImportError: - LDAP_CONFIGURED = False - -# LDAP configuration (optional) -if LDAP_CONFIGURED: - try: - import ldap - import django_auth_ldap - # Prepend LDAPBackend to the default ModelBackend - AUTHENTICATION_BACKENDS = [ - 'django_auth_ldap.backend.LDAPBackend', - 'utilities.auth_backends.ViewExemptModelBackend', - ] - # Optionally disable strict certificate checking - if LDAP_IGNORE_CERT_ERRORS: - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) - # Enable logging for django_auth_ldap - ldap_logger = logging.getLogger('django_auth_ldap') - ldap_logger.addHandler(logging.StreamHandler()) - ldap_logger.setLevel(logging.DEBUG) - except ImportError: - raise ImproperlyConfigured( - "LDAP authentication has been configured, but django-auth-ldap is not installed. You can remove " - "netbox/ldap_config.py to disable LDAP." - ) -else: - AUTHENTICATION_BACKENDS = [ - 'utilities.auth_backends.ViewExemptModelBackend', - ] +# # Database -configuration.DATABASE.update({'ENGINE': 'django.db.backends.postgresql'}) +# + +# Only PostgreSQL is supported +DATABASE.update({ + 'ENGINE': 'django.db.backends.postgresql' +}) + DATABASES = { - 'default': configuration.DATABASE, + 'default': DATABASE, } -# Sessions -if LOGIN_TIMEOUT is not None: - if type(LOGIN_TIMEOUT) is not int or LOGIN_TIMEOUT < 0: - raise ImproperlyConfigured( - "LOGIN_TIMEOUT must be a positive integer (value: {})".format(LOGIN_TIMEOUT) - ) - # Django default is 1209600 seconds (14 days) - SESSION_COOKIE_AGE = LOGIN_TIMEOUT -if SESSION_FILE_PATH is not None: - SESSION_ENGINE = 'django.contrib.sessions.backends.file' +# # Redis +# + REDIS_HOST = REDIS.get('HOST', 'localhost') REDIS_PORT = REDIS.get('PORT', 6379) REDIS_PASSWORD = REDIS.get('PASSWORD', '') @@ -140,7 +119,22 @@ REDIS_CACHE_DATABASE = REDIS.get('CACHE_DATABASE', 1) REDIS_DEFAULT_TIMEOUT = REDIS.get('DEFAULT_TIMEOUT', 300) REDIS_SSL = REDIS.get('SSL', False) + +# +# Sessions +# + +if LOGIN_TIMEOUT is not None: + # Django default is 1209600 seconds (14 days) + SESSION_COOKIE_AGE = LOGIN_TIMEOUT +if SESSION_FILE_PATH is not None: + SESSION_ENGINE = 'django.contrib.sessions.backends.file' + + +# # Email +# + EMAIL_HOST = EMAIL.get('SERVER') EMAIL_PORT = EMAIL.get('PORT', 25) EMAIL_HOST_USER = EMAIL.get('USERNAME') @@ -149,7 +143,11 @@ EMAIL_TIMEOUT = EMAIL.get('TIMEOUT', 10) SERVER_EMAIL = EMAIL.get('FROM_EMAIL') EMAIL_SUBJECT_PREFIX = '[NetBox] ' -# Installed applications + +# +# Django +# + INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -203,10 +201,11 @@ MIDDLEWARE = ( ROOT_URLCONF = 'netbox.urls' +TEMPLATES_DIR = BASE_DIR + '/templates' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR + '/templates'], + 'DIRS': [TEMPLATES_DIR], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -221,7 +220,117 @@ TEMPLATES = [ }, ] +# Authentication +AUTHENTICATION_BACKENDS = [ + 'utilities.auth_backends.ViewExemptModelBackend', +] + +# Internationalization +LANGUAGE_CODE = 'en-us' +USE_I18N = True +USE_TZ = True + +# WSGI +WSGI_APPLICATION = 'netbox.wsgi.application' +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +USE_X_FORWARDED_HOST = True + +# Static files (CSS, JavaScript, Images) +STATIC_ROOT = BASE_DIR + '/static' +STATIC_URL = '/{}static/'.format(BASE_PATH) +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, "project-static"), +) + +# Media +MEDIA_URL = '/{}media/'.format(BASE_PATH) + +# Disable default limit of 1000 fields per request. Needed for bulk deletion of objects. (Added in Django 1.10.) +DATA_UPLOAD_MAX_NUMBER_FIELDS = None + +# Messages +MESSAGE_TAGS = { + messages.ERROR: 'danger', +} + +# Authentication URLs +LOGIN_URL = '/{}login/'.format(BASE_PATH) + +CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS + + +# +# LDAP authentication (optional) +# + +try: + from netbox import ldap_config as LDAP_CONFIG +except ImportError: + LDAP_CONFIG = None + +if LDAP_CONFIG is not None: + + # Check that django_auth_ldap is installed + try: + import ldap + import django_auth_ldap + except ImportError: + raise ImproperlyConfigured( + "LDAP authentication has been configured, but django-auth-ldap is not installed. Remove " + "netbox/ldap_config.py to disable LDAP." + ) + + # Required configuration parameters + try: + AUTH_LDAP_SERVER_URI = getattr(LDAP_CONFIG, 'AUTH_LDAP_SERVER_URI') + except AttributeError: + raise ImproperlyConfigured( + "Required parameter AUTH_LDAP_SERVER_URI is missing from ldap_config.py." + ) + + # Optional configuration parameters + AUTH_LDAP_ALWAYS_UPDATE_USER = getattr(LDAP_CONFIG, 'AUTH_LDAP_ALWAYS_UPDATE_USER', True) + AUTH_LDAP_AUTHORIZE_ALL_USERS = getattr(LDAP_CONFIG, 'AUTH_LDAP_AUTHORIZE_ALL_USERS', False) + AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = getattr(LDAP_CONFIG, 'AUTH_LDAP_BIND_AS_AUTHENTICATING_USER', False) + AUTH_LDAP_BIND_DN = getattr(LDAP_CONFIG, 'AUTH_LDAP_BIND_DN', '') + AUTH_LDAP_BIND_PASSWORD = getattr(LDAP_CONFIG, 'AUTH_LDAP_BIND_PASSWORD', '') + AUTH_LDAP_CACHE_TIMEOUT = getattr(LDAP_CONFIG, 'AUTH_LDAP_CACHE_TIMEOUT', 0) + AUTH_LDAP_CONNECTION_OPTIONS = getattr(LDAP_CONFIG, 'AUTH_LDAP_CONNECTION_OPTIONS', {}) + AUTH_LDAP_DENY_GROUP = getattr(LDAP_CONFIG, 'AUTH_LDAP_DENY_GROUP', None) + AUTH_LDAP_FIND_GROUP_PERMS = getattr(LDAP_CONFIG, 'AUTH_LDAP_FIND_GROUP_PERMS', False) + AUTH_LDAP_GLOBAL_OPTIONS = getattr(LDAP_CONFIG, 'AUTH_LDAP_GLOBAL_OPTIONS', {}) + AUTH_LDAP_GROUP_SEARCH = getattr(LDAP_CONFIG, 'AUTH_LDAP_GROUP_SEARCH', None) + AUTH_LDAP_GROUP_TYPE = getattr(LDAP_CONFIG, 'AUTH_LDAP_GROUP_TYPE', None) + AUTH_LDAP_MIRROR_GROUPS = getattr(LDAP_CONFIG, 'AUTH_LDAP_MIRROR_GROUPS', None) + AUTH_LDAP_MIRROR_GROUPS_EXCEPT = getattr(LDAP_CONFIG, 'AUTH_LDAP_MIRROR_GROUPS_EXCEPT', None) + AUTH_LDAP_PERMIT_EMPTY_PASSWORD = getattr(LDAP_CONFIG, 'AUTH_LDAP_PERMIT_EMPTY_PASSWORD', False) + AUTH_LDAP_REQUIRE_GROUP = getattr(LDAP_CONFIG, 'AUTH_LDAP_REQUIRE_GROUP', None) + AUTH_LDAP_NO_NEW_USERS = getattr(LDAP_CONFIG, 'AUTH_LDAP_NO_NEW_USERS', False) + AUTH_LDAP_START_TLS = getattr(LDAP_CONFIG, 'AUTH_LDAP_START_TLS', False) + AUTH_LDAP_USER_QUERY_FIELD = getattr(LDAP_CONFIG, 'AUTH_LDAP_USER_QUERY_FIELD', None) + AUTH_LDAP_USER_ATTRLIST = getattr(LDAP_CONFIG, 'AUTH_LDAP_USER_ATTRLIST', None) + AUTH_LDAP_USER_ATTR_MAP = getattr(LDAP_CONFIG, 'AUTH_LDAP_USER_ATTR_MAP', {}) + AUTH_LDAP_USER_DN_TEMPLATE = getattr(LDAP_CONFIG, 'AUTH_LDAP_USER_DN_TEMPLATE', None) + AUTH_LDAP_USER_FLAGS_BY_GROUP = getattr(LDAP_CONFIG, 'AUTH_LDAP_USER_FLAGS_BY_GROUP', {}) + AUTH_LDAP_USER_SEARCH = getattr(LDAP_CONFIG, 'AUTH_LDAP_USER_SEARCH', None) + + # Optionally disable strict certificate checking + if getattr(LDAP_CONFIG, 'LDAP_IGNORE_CERT_ERRORS', False): + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) + + # Prepend LDAPBackend to the authentication backends list + AUTHENTICATION_BACKENDS.insert(0, 'django_auth_ldap.backend.LDAPBackend') + + # Enable logging for django_auth_ldap + ldap_logger = logging.getLogger('django_auth_ldap') + ldap_logger.addHandler(logging.StreamHandler()) + ldap_logger.setLevel(logging.DEBUG) + + +# # Caching +# + if REDIS_SSL: REDIS_CACHE_CON_STRING = 'rediss://' else: @@ -255,53 +364,19 @@ CACHEOPS = { } CACHEOPS_DEGRADE_ON_FAILURE = True -# WSGI -WSGI_APPLICATION = 'netbox.wsgi.application' -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -USE_X_FORWARDED_HOST = True - -# Internationalization -LANGUAGE_CODE = 'en-us' -USE_I18N = True -USE_TZ = True - -# Static files (CSS, JavaScript, Images) -STATIC_ROOT = BASE_DIR + '/static' -STATIC_URL = '/{}static/'.format(BASE_PATH) -STATICFILES_DIRS = ( - os.path.join(BASE_DIR, "project-static"), -) - -# Media -MEDIA_URL = '/{}media/'.format(BASE_PATH) - -# Disable default limit of 1000 fields per request. Needed for bulk deletion of objects. (Added in Django 1.10.) -DATA_UPLOAD_MAX_NUMBER_FIELDS = None - -# Messages -MESSAGE_TAGS = { - messages.ERROR: 'danger', -} - -# Authentication URLs -LOGIN_URL = '/{}login/'.format(BASE_PATH) - -# Secrets -SECRETS_MIN_PUBKEY_SIZE = 2048 - -# Pagination -PER_PAGE_DEFAULTS = [ - 25, 50, 100, 250, 500, 1000 -] -if PAGINATE_COUNT not in PER_PAGE_DEFAULTS: - PER_PAGE_DEFAULTS.append(PAGINATE_COUNT) - PER_PAGE_DEFAULTS = sorted(PER_PAGE_DEFAULTS) +# # Django filters +# + FILTERS_NULL_CHOICE_LABEL = 'None' FILTERS_NULL_CHOICE_VALUE = 'null' + +# # Django REST framework (API) +# + REST_FRAMEWORK_VERSION = VERSION[0:3] # Use major.minor as API version REST_FRAMEWORK = { 'ALLOWED_VERSIONS': [REST_FRAMEWORK_VERSION], @@ -326,19 +401,11 @@ REST_FRAMEWORK = { 'VIEW_NAME_FUNCTION': 'netbox.api.get_view_name', } -# Django RQ (Webhooks backend) -RQ_QUEUES = { - 'default': { - 'HOST': REDIS_HOST, - 'PORT': REDIS_PORT, - 'DB': REDIS_DATABASE, - 'PASSWORD': REDIS_PASSWORD, - 'DEFAULT_TIMEOUT': REDIS_DEFAULT_TIMEOUT, - 'SSL': REDIS_SSL, - } -} -# drf_yasg settings for Swagger +# +# drf_yasg (OpenAPI/Swagger) +# + SWAGGER_SETTINGS = { 'DEFAULT_AUTO_SCHEMA_CLASS': 'utilities.custom_inspectors.NetBoxSwaggerAutoSchema', 'DEFAULT_FIELD_INSPECTORS': [ @@ -377,14 +444,43 @@ SWAGGER_SETTINGS = { } +# +# Django RQ (Webhooks backend) +# + +RQ_QUEUES = { + 'default': { + 'HOST': REDIS_HOST, + 'PORT': REDIS_PORT, + 'DB': REDIS_DATABASE, + 'PASSWORD': REDIS_PASSWORD, + 'DEFAULT_TIMEOUT': REDIS_DEFAULT_TIMEOUT, + 'SSL': REDIS_SSL, + } +} + + +# # Django debug toolbar +# + INTERNAL_IPS = ( '127.0.0.1', '::1', ) -try: - HOSTNAME = socket.gethostname() -except Exception: - HOSTNAME = 'localhost' +# +# NetBox internal settings +# + +# Secrets +SECRETS_MIN_PUBKEY_SIZE = 2048 + +# Pagination +PER_PAGE_DEFAULTS = [ + 25, 50, 100, 250, 500, 1000 +] +if PAGINATE_COUNT not in PER_PAGE_DEFAULTS: + PER_PAGE_DEFAULTS.append(PAGINATE_COUNT) + PER_PAGE_DEFAULTS = sorted(PER_PAGE_DEFAULTS)