# -*- encoding: utf-8 -*- # ============LICENSE_START======================================================= # org.onap.vvp/engagementmgr # =================================================================== # Copyright © 2017 AT&T Intellectual Property. All rights reserved. # =================================================================== # # Unless otherwise specified, all software contained herein is licensed # under the Apache License, Version 2.0 (the “License”); # you may not use this software except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # # Unless otherwise specified, all documentation contained herein is licensed # under the Creative Commons License, Attribution 4.0 Intl. (the “License”); # you may not use this documentation except in compliance with the License. # You may obtain a copy of the License at # # https://creativecommons.org/licenses/by/4.0/ # # Unless required by applicable law or agreed to in writing, documentation # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # ============LICENSE_END============================================ # # ECOMP is a trademark and service mark of AT&T Intellectual Property. --- kind: ConfigMap apiVersion: v1 metadata: name: em-settings namespace: default data: uwsgi.ini: | [uwsgi] uwsgi-socket = :80 plugin = python chdir = /srv module = vvp.wsgi:application master = True pidfile = /tmp/project-master.pid vacuum = True max-requests = 5000 enable-threads = True stats = 0.0.0.0:9000 stats-http = True __init__.py: | """ Django settings for VVP project. Environment variables that must exist: ENVIRONMENT SECRET_KEY SECRET_WEBHOOK_TOKEN SECRET_GITLAB_AUTH_TOKEN SECRET_JENKINS_PASSWORD SECRET_CMS_APP_CLIENT_ID SECRET_CMS_APP_CLIENT_SECRET Environment variables that must exist in production: EMAIL_HOST EMAIL_HOST_PASSWORD EMAIL_HOST_USER EMAIL_PORT """ import os from vvp.settings.envbool import envbool from corsheaders.defaults import default_headers from boto.s3.connection import OrdinaryCallingFormat import datetime # With this file at ice/settings/__init__.py, we need three applications of # dirname() to find the project root. import engagementmanager PROJECT_PATH = os.path.dirname(os.path.dirname(engagementmanager.__file__)) LOGS_PATH = os.path.join(PROJECT_PATH, "logs") ENVIRONMENT = os.environ['ENVIRONMENT'] PROGRAM_NAME_URL_PREFIX = os.environ['PROGRAM_NAME_URL_PREFIX'] SERVICE_PROVIDER = os.environ['SERVICE_PROVIDER'] PROGRAM_NAME = os.environ['PROGRAM_NAME'] SERVICE_PROVIDER_DOMAIN = os.environ['SERVICE_PROVIDER_DOMAIN'] # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ SECRET_KEY = os.environ["SECRET_KEY"] # https://docs.djangoproject.com/en/1.10/ref/settings/#allowed-hosts # Anything in the Host header that does not match our expected domain should # raise SuspiciousOperation exception. ALLOWED_HOSTS = ['*'] DEBUG = envbool('DJANGO_DEBUG_MODE', False) if ENVIRONMENT == 'production': EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = os.environ['EMAIL_HOST'] EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD'] EMAIL_HOST_USER = os.environ['EMAIL_HOST_USER'] EMAIL_PORT = os.environ['EMAIL_PORT'] else: EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # Note: Only SSL email backends are allowed EMAIL_USE_SSL = True REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'EXCEPTION_HANDLER': 'engagementmanager.utils.exception_handler.ice_exception_handler', 'PAGE_SIZE': 10, 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', ), 'DEFAULT_PARSER_CLASSES': ( 'engagementmanager.rest.parsers.XSSJSONParser', 'engagementmanager.rest.parsers.XSSFormParser', 'engagementmanager.rest.parsers.XSSMultiPartParser', ) } JWT_AUTH = { 'JWT_AUTH_HEADER_PREFIX': 'token', 'JWT_ALGORITHM': 'HS256', 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), 'JWT_DECODE_HANDLER': 'engagementmanager.utils.authentication.ice_jwt_decode_handler', } APPEND_SLASH = False # Application definition INSTALLED_APPS = [ 'django.contrib.auth', # required by d.c.admin 'corsheaders', 'django.contrib.contenttypes', # required by d.c.admin 'django.contrib.sessions', # required by d.c.admin 'django.contrib.messages', # required by d.c.admin 'django.contrib.staticfiles', 'django.contrib.admin', # django admin site 'rest_framework', 'engagementmanager.apps.EngagementmanagerConfig', 'validationmanager.apps.ValidationmanagerConfig', ] MIDDLEWARE_CLASSES = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', # required by d.c.admin 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'corsheaders.middleware.CorsMiddleware', ] ROOT_URLCONF = 'vvp.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [PROJECT_PATH + '/web/templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', # required by d.c.admin 'django.contrib.messages.context_processors.messages', # required by d.c.admin ], }, }, ] WSGI_APPLICATION = 'vvp.wsgi.application' # Database # https://docs.djangoproject.com/en/1.9/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ['PGDATABASE'], 'USER': os.environ['PGUSER'], 'PASSWORD': os.environ['PGPASSWORD'], 'HOST': os.environ['PGHOST'], 'PORT': os.environ['PGPORT'], } } # Password validation # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ {'NAME': 'django.contrib.auth.password_validation.%s' % s} for s in [ 'UserAttributeSimilarityValidator', 'MinimumLengthValidator', 'CommonPasswordValidator', 'NumericPasswordValidator', ]] # Internationalization # https://docs.djangoproject.com/en/1.9/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True CORS_ALLOW_HEADERS = default_headers + ('ICE-USER-ID',) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.9/howto/static-files/ STATIC_ROOT = os.environ['STATIC_ROOT'] LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { # All possible attributes are: https://docs.python.org/3/library/logging.html#logrecord-attributes 'verbose': { 'format': '%(asctime)s %(levelname)s %(name)s %(module)s %(lineno)d %(process)d %(thread)d %(message)s' }, 'simple': { 'format': '%(asctime)s %(levelname)s %(name)s %(message)s' }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'vvp-info.log': { 'level': 'INFO', # handler will ignore DEBUG (only process INFO, WARN, ERROR, CRITICAL, FATAL) 'class': 'logging.FileHandler', 'filename': os.path.join(LOGS_PATH, 'vvp-info.log'), 'formatter': 'verbose' }, 'vvp-debug.log': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': os.path.join(LOGS_PATH, 'vvp-debug.log'), 'formatter': 'verbose' }, 'vvp-requests.log': { 'level': 'ERROR', 'class': 'logging.FileHandler', 'filename': os.path.join(LOGS_PATH, 'vvp-requests.log'), 'formatter': 'verbose' }, 'vvp-db.log': { 'level': 'ERROR', 'class': 'logging.FileHandler', 'filename': os.path.join(LOGS_PATH, 'vvp-db.log'), 'formatter': 'verbose', }, }, 'loggers': { 'vvp.logger': { 'handlers': ['vvp-info.log', 'vvp-debug.log', 'vvp-requests.log', 'vvp-db.log', 'console'], 'level': 'DEBUG' if DEBUG else 'INFO', }, 'django': { 'handlers': ['console'], 'level': 'INFO' if DEBUG else 'ERROR', }, 'django.request': { 'handlers': ['vvp-requests.log', 'console'], 'level': 'INFO' if DEBUG else 'ERROR', }, 'django.db.backends': { 'handlers': ['vvp-db.log', 'console'], 'level': 'DEBUG' if DEBUG else 'ERROR', 'propagate': False, }, # silence the hundred lines of useless "missing variable in template" # complaints per admin pageview. 'django.template': { 'level': 'DEBUG', 'handlers': ['vvp-info.log', 'vvp-debug.log', 'console'], 'propagate': False, }, } } ############################# # VVP Related Configuration ############################# CONTACT_FROM_ADDRESS = os.getenv('CONTACT_FROM_ADDRESS', 'dummy@example.com') CONTACT_EMAILS = [s.strip() for s in os.getenv('CONTACT_EMAILS', 'user@example.com').split(',') if s] DOMAIN = os.getenv('EM_DOMAIN_NAME') TOKEN_EXPIRATION_IN_HOURS = 48 DAILY_SCHEDULED_JOB_HOUR = 20 NUMBER_OF_POLLED_ACTIVITIES = 5 TEMP_PASSWORD_EXPIRATION_IN_HOURS = 48 # This is the DNS name pointing to the private-network ip of the host machine # running (a haproxy that points to) (an nginx frontend for) this app API_DOMAIN = 'em' # The authentication token needed by Jenkins or Gitlab to issue webhook updates # to us. This is a "secret" shared by Jenkins and Django. It must be part of # the URL path component for the Jenkins webhook in ValidationManager to accept # a notification. It should be a set of random URL-path-safe characters, with # no slash '/'. # FIXME: Does this authentication scheme actually gain us anything? What's the # threat model WEBHOOK_TOKEN = os.environ['SECRET_WEBHOOK_TOKEN'] # The authentication token and URL needed for us to issue requests to the GitLab API. GITLAB_TOKEN = os.environ['SECRET_GITLAB_AUTH_TOKEN'] GITLAB_URL = "http://gitlab/" JENKINS_URL = "http://jenkins:8080/" JENKINS_USERNAME = "admin" JENKINS_PASSWORD = os.environ['SECRET_JENKINS_PASSWORD'] IS_CL_CREATED_ON_REVIEW_STATE = envbool('IS_CL_CREATED_ON_REVIEW_STATE', False) # Options: True, False IS_SIGNAL_ENABLED = envbool('IS_SIGNAL_ENABLED', True) RECENT_ENG_TTL = 3 # In days CMS_URL = "http://cms/api/" CMS_APP_CLIENT_ID = os.environ['SECRET_CMS_APP_CLIENT_ID'] CMS_APP_CLIENT_SECRET = os.environ['SECRET_CMS_APP_CLIENT_SECRET'] # slack integration SLACK_API_TOKEN = os.environ['SLACK_API_TOKEN'] ENGAGEMENTS_CHANNEL = os.getenv('ENGAGEMENTS_CHANNEL', '') ENGAGEMENTS_NOTIFICATIONS_CHANNEL = os.getenv('ENGAGEMENTS_NOTIFICATIONS_CHANNEL:', '') DEVOPS_CHANNEL = os.getenv('DEVOPS_CHANNEL', '') DEVOPS_NOTIFICATIONS_CHANNEL = os.getenv('DEVOPS_NOTIFICATIONS_CHANNEL', '') # S3 configuration for static resources storage and media upload # used by our custom storage.py MEDIA_BUCKET = "em-media" STATIC_BUCKET = "em-static" # django-storages configuration AWS_S3_HOST = os.environ['S3_HOST'] AWS_S3_PORT = int(os.environ['S3_PORT']) AWS_S3_CUSTOM_DOMAIN = os.environ['S3_HOST'] AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID'] AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY'] AWS_AUTO_CREATE_BUCKET = True AWS_PRELOAD_METADATA = True # Set by custom subclass. # AWS_STORAGE_BUCKET_NAME = "em-static" AWS_S3_CALLING_FORMAT = OrdinaryCallingFormat() DEFAULT_FILE_STORAGE = 'vvp.settings.storage.S3MediaStorage' STATICFILES_STORAGE = 'vvp.settings.storage.S3StaticStorage' # These seem to have no effect even when we don't override with custom_domain? STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, STATIC_BUCKET) MEDIA_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, MEDIA_BUCKET) STATIC_ROOT = os.environ['STATIC_ROOT'] storage.py: | """ storage.py In order to make Django store trusted static files and untrusted media (user-uploaded) files in separate s3 buckets, we must create two different storage classes. https://www.caktusgroup.com/blog/2014/11/10/Using-Amazon-S3-to-store-your-Django-sites-static-and-media-files/ http://www.leehodgkinson.com/blog/my-mezzanine-s3-setup/ """ # FIXME this module never changes so might not need not be kept in a # configmap. Also it is (almost) the same as what we use in cms. # There is a newer storage based on boto3 but that doesn't support changing # the HOST, as we need to for non-amazon s3 services. It does support an # "endpoint"; setting AWS_S3_ENDPOINT_URL may cause it to work. from storages.backends.s3boto import S3BotoStorage from django.conf import settings # NOTE for some reason, collectstatic uploads to bucket/location but the # urls constructed are domain/location class S3StaticStorage(S3BotoStorage): custom_domain = '%s/%s' % (settings.AWS_S3_HOST, settings.STATIC_BUCKET) bucket_name = settings.STATIC_BUCKET # location = ... class S3MediaStorage(S3BotoStorage): custom_domain = '%s/%s' % (settings.AWS_S3_HOST, settings.MEDIA_BUCKET) bucket_name = settings.MEDIA_BUCKET # location = ... envbool.py: | """ envbool.py Return which environment is currently running on (to setting.py). """ import os def envbool(key, default=False, unknown=True): """Return a boolean value based on that of an environment variable. Environment variables have no native boolean type. They are always strings, and may be empty or unset (which differs from empty.) Furthermore, notions of what is "truthy" in shell script differ from that of python. This function converts environment variables to python boolean True or False in case-insensitive, expected ways to avoid pitfalls: "True", "true", and "1" become True "False", "false", and "0" become False unset or empty becomes False by default (toggle with 'default' parameter.) any other value becomes True by default (toggle with 'unknown' parameter.) """ return { 'true': True, '1': True, # 't': True, 'false': False, '0': False, # 'f': False. '': default, }.get(os.getenv(key, '').lower(), unknown)