diff --git a/.dockerignore b/.dockerignore index 2efd33b..de79042 100644 --- a/.dockerignore +++ b/.dockerignore @@ -97,6 +97,7 @@ npm-debug.log Dockerfile* docker-compose* .dockerignore +docker/.autoinit # Git .git diff --git a/.gitignore b/.gitignore index 4a62dbb..4b46523 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ nosetests.xml flask config.py configs/production.py +docker/.autoinit logfile.log log.txt pdns.db diff --git a/README.md b/README.md index bec39a5..5008836 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,18 @@ # PowerDNS-Admin -A PowerDNS web interface with advanced features. [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/ngoduykhanh/PowerDNS-Admin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ngoduykhanh/PowerDNS-Admin/context:python) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ngoduykhanh/PowerDNS-Admin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ngoduykhanh/PowerDNS-Admin/context:javascript) +The PowerDNS-Admin is a simple web GUI for managing zone configurations of a PowerDNS Authoritative server. + +The PowerDNS-Admin app does NOT modify the PowerDNS Authoritative server database directly. Instead, it communicates with the PDNS server via the built-in HTTP API. + +The app does have a database for identity management, access control, and caching which can be hosted in either MySQL or SQLite. + +- [PowerDNS-Admin GitHub](https://github.com/ngoduykhanh/PowerDNS-Admin) +- [PowerDNS-Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) +- [PowerDNS-Admin Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki) + #### Features: - Multiple domain management - Domain template @@ -19,38 +28,16 @@ A PowerDNS web interface with advanced features. - Limited API for manipulating zones and records - Full IDN/Punycode support -## Running PowerDNS-Admin -There are several ways to run PowerDNS-Admin. The easiest way is to use Docker. -If you are looking to install and run PowerDNS-Admin directly onto your system check out the [Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki#installation-guides) for ways to do that. +## Deploying PowerDNS-Admin +There are multiple ways to run the PowerDNS-Admin app. The recommended method is to use the official [Docker images](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/docker.md). -### Docker -This are two options to run PowerDNS-Admin using Docker. -To get started as quickly as possible try option 1. If you want to make modifications to the configuration option 2 may be cleaner. +If you would like to run PowerDNS-Admin directly on your machine or VM, check out the [Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki#installation-guides) for additional information. -#### Option 1: From Docker Hub -The easiest is to just run the latest Docker image from Docker Hub: -``` -$ docker run -d \ - -e SECRET_KEY='a-very-secret-key' \ - -v pda-data:/data \ - -p 9191:80 \ - ngoduykhanh/powerdns-admin:latest -``` -This creates a volume called `pda-data` to persist the SQLite database with the configuration. +Once you have deployed the app through one of the supported methods, You should be able to access the PowerDNS-Admin app by pointing your browser to http://localhost:8080. -#### Option 2: Using docker-compose -1. Update the configuration - Edit the `docker-compose.yml` file to update the database connection string in `SQLALCHEMY_DATABASE_URI`. - Other environment variables are mentioned in the [legal_envvars](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/configs/docker_config.py#L5-L46). - To use the Docker secrets feature it is possible to append `_FILE` to the environment variables and point to a file with the values stored in it. - Make sure to set the environment variable `SECRET_KEY` to a long random string (https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY) +## Configuring PowerDNS-Admin -2. Start docker container - ``` - $ docker-compose up - ``` - -You can then access PowerDNS-Admin by pointing your browser to http://localhost:9191. +The app has a plethora of settings that may be configured through a number of methods. Check out the settings documentation [here](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md). ## Screenshots ![dashboard](https://user-images.githubusercontent.com/6447444/44068603-0d2d81f6-9fa5-11e8-83af-14e2ad79e370.png) diff --git a/configs/default.py b/configs/default.py new file mode 100644 index 0000000..e69de29 diff --git a/configs/development.py b/configs/development.py index 2c2e63d..5a93b40 100644 --- a/configs/development.py +++ b/configs/development.py @@ -1,33 +1,35 @@ -import os -#import urllib.parse -basedir = os.path.abspath(os.path.dirname(__file__)) - -### BASIC APP CONFIG +######################################## +# Core App Config +######################################## +PDA_BIND_ADDRESS = '0.0.0.0' +PDA_BIND_PORT = 80 +PDA_DEBUG = True SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu' SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2' -BIND_ADDRESS = '0.0.0.0' -PORT = 9191 +SIGNUP_ENABLED = True +HSTS_ENABLED = False OFFLINE_MODE = False +FILESYSTEM_SESSIONS_ENABLED = False -### DATABASE CONFIG +######################################## +# MySQL Database Config +######################################## +SQLA_DB_HOST = 'mysql' +SQLA_DB_PORT = 3306 SQLA_DB_USER = 'pda' -SQLA_DB_PASSWORD = 'changeme' -SQLA_DB_HOST = '127.0.0.1' +SQLA_DB_PASSWORD = 'qHZDZ26fPqqCfKZtWkQ9' SQLA_DB_NAME = 'pda' SQLALCHEMY_TRACK_MODIFICATIONS = True -### DATABASE - MySQL -#SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( -# urllib.parse.quote_plus(SQLA_DB_USER), -# urllib.parse.quote_plus(SQLA_DB_PASSWORD), -# SQLA_DB_HOST, -# SQLA_DB_NAME -#) +######################################## +# SQLite Database Config +######################################## +# Uncomment the following line to use SQLite instead of MySQL +# SQLALCHEMY_DATABASE_URI = 'sqlite:////srv/app/pdns.db' -### DATABASE - SQLite -SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') - -### SMTP config +######################################## +# SMTP Config +######################################## # MAIL_SERVER = 'localhost' # MAIL_PORT = 25 # MAIL_DEBUG = False @@ -37,28 +39,27 @@ SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') # MAIL_PASSWORD = None # MAIL_DEFAULT_SENDER = ('PowerDNS-Admin', 'noreply@domain.ltd') -# SAML Authnetication +######################################## +# SAML Authentication Config +######################################## SAML_ENABLED = False # SAML_DEBUG = True # SAML_PATH = os.path.join(os.path.dirname(__file__), 'saml') -# ##Example for ADFS Metadata-URL +# ** Example for ADFS Metadata-URL # SAML_METADATA_URL = 'https:///FederationMetadata/2007-06/FederationMetadata.xml' -# #Cache Lifetime in Seconds +# ** Cache Lifetime in Seconds # SAML_METADATA_CACHE_LIFETIME = 1 - -# # SAML SSO binding format to use -# ## Default: library default (urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect) +# ** SAML SSO binding format to use +# *** Default: library default (urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect) # #SAML_IDP_SSO_BINDING = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' - -# ## EntityID of the IdP to use. Only needed if more than one IdP is -# ## in the SAML_METADATA_URL -# ### Default: First (only) IdP in the SAML_METADATA_URL -# ### Example: https://idp.example.edu/idp -# #SAML_IDP_ENTITY_ID = 'https://idp.example.edu/idp' -# ## NameID format to request -# ### Default: The SAML NameID Format in the metadata if present, -# ### otherwise urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified -# ### Example: urn:oid:0.9.2342.19200300.100.1.1 +# ** EntityID of the IdP to use. Only needed if more than one IdP is in the SAML_METADATA_URL +# *** Default: First (only) IdP in the SAML_METADATA_URL +# *** Example: https://idp.example.edu/idp +# SAML_IDP_ENTITY_ID = 'https://idp.example.edu/idp' +# ** NameID format to request +# *** Default: The SAML NameID Format in the metadata if present, +# *** otherwise urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified +# *** Example: urn:oid:0.9.2342.19200300.100.1.1 # #SAML_NAMEID_FORMAT = 'urn:oid:0.9.2342.19200300.100.1.1' # Following parameter defines RequestedAttributes section in SAML metadata diff --git a/configs/docker_config.py b/configs/docker_config.py deleted file mode 100644 index 6666fc2..0000000 --- a/configs/docker_config.py +++ /dev/null @@ -1,115 +0,0 @@ -# Defaults for Docker image -BIND_ADDRESS = '0.0.0.0' -PORT = 80 -SQLALCHEMY_DATABASE_URI = 'sqlite:////data/powerdns-admin.db' - -legal_envvars = ( - 'SECRET_KEY', - 'OIDC_OAUTH_API_URL', - 'OIDC_OAUTH_TOKEN_URL', - 'OIDC_OAUTH_AUTHORIZE_URL', - 'BIND_ADDRESS', - 'PORT', - 'LOG_LEVEL', - 'SALT', - 'SQLALCHEMY_TRACK_MODIFICATIONS', - 'SQLALCHEMY_DATABASE_URI', - 'MAIL_SERVER', - 'MAIL_PORT', - 'MAIL_DEBUG', - 'MAIL_USE_TLS', - 'MAIL_USE_SSL', - 'MAIL_USERNAME', - 'MAIL_PASSWORD', - 'MAIL_DEFAULT_SENDER', - 'SAML_ENABLED', - 'SAML_DEBUG', - 'SAML_PATH', - 'SAML_METADATA_URL', - 'SAML_METADATA_CACHE_LIFETIME', - 'SAML_IDP_SSO_BINDING', - 'SAML_IDP_ENTITY_ID', - 'SAML_NAMEID_FORMAT', - 'SAML_ATTRIBUTE_EMAIL', - 'SAML_ATTRIBUTE_GIVENNAME', - 'SAML_ATTRIBUTE_SURNAME', - 'SAML_ATTRIBUTE_NAME', - 'SAML_ATTRIBUTE_USERNAME', - 'SAML_ATTRIBUTE_ADMIN', - 'SAML_ATTRIBUTE_GROUP', - 'SAML_GROUP_ADMIN_NAME', - 'SAML_GROUP_TO_ACCOUNT_MAPPING', - 'SAML_ATTRIBUTE_ACCOUNT', - 'SAML_SP_ENTITY_ID', - 'SAML_SP_CONTACT_NAME', - 'SAML_SP_CONTACT_MAIL', - 'SAML_SIGN_REQUEST', - 'SAML_WANT_MESSAGE_SIGNED', - 'SAML_LOGOUT', - 'SAML_LOGOUT_URL', - 'SAML_ASSERTION_ENCRYPTED', - 'OFFLINE_MODE', - 'REMOTE_USER_LOGOUT_URL', - 'REMOTE_USER_COOKIES', - 'SIGNUP_ENABLED', - 'LOCAL_DB_ENABLED', - 'LDAP_ENABLED', - 'SAML_CERT', - 'SAML_KEY', - 'FILESYSTEM_SESSIONS_ENABLED' -) - -legal_envvars_int = ('PORT', 'MAIL_PORT', 'SAML_METADATA_CACHE_LIFETIME') - -legal_envvars_bool = ( - 'SQLALCHEMY_TRACK_MODIFICATIONS', - 'HSTS_ENABLED', - 'MAIL_DEBUG', - 'MAIL_USE_TLS', - 'MAIL_USE_SSL', - 'SAML_ENABLED', - 'SAML_DEBUG', - 'SAML_SIGN_REQUEST', - 'SAML_WANT_MESSAGE_SIGNED', - 'SAML_LOGOUT', - 'SAML_ASSERTION_ENCRYPTED', - 'OFFLINE_MODE', - 'REMOTE_USER_ENABLED', - 'SIGNUP_ENABLED', - 'LOCAL_DB_ENABLED', - 'LDAP_ENABLED', - 'FILESYSTEM_SESSIONS_ENABLED' -) - -# import everything from environment variables -import os -import sys - - -def str2bool(v): - return v.lower() in ("true", "yes", "1") - - -for v in legal_envvars: - - ret = None - # _FILE suffix will allow to read value from file, usefull for Docker's - # secrets feature - if v + '_FILE' in os.environ: - if v in os.environ: - raise AttributeError( - "Both {} and {} are set but are exclusive.".format( - v, v + '_FILE')) - with open(os.environ[v + '_FILE']) as f: - ret = f.read() - f.close() - - elif v in os.environ: - ret = os.environ[v] - - if ret is not None: - if v in legal_envvars_bool: - ret = str2bool(ret) - if v in legal_envvars_int: - ret = int(ret) - sys.modules[__name__].__dict__[v] = ret diff --git a/docker-compose-test.yml b/docker-compose-test.yml deleted file mode 100644 index 7dcf4a0..0000000 --- a/docker-compose-test.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: "2.1" - -services: - powerdns-admin: - build: - context: . - dockerfile: docker-test/Dockerfile - image: powerdns-admin-test - container_name: powerdns-admin-test - ports: - - "9191:80" - networks: - - default - env_file: - - ./docker-test/env - depends_on: - - pdns-server - - pdns-server: - build: - context: . - dockerfile: docker-test/Dockerfile.pdns - image: pdns-server-test - ports: - - "5053:53" - - "5053:53/udp" - - "8081:8081" - networks: - - default - env_file: - - ./docker-test/env - -networks: - default: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e18d683..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - app: - image: ngoduykhanh/powerdns-admin:latest - container_name: powerdns_admin - ports: - - "9191:80" - logging: - driver: json-file - options: - max-size: 50m - environment: - - SQLALCHEMY_DATABASE_URI=mysql://pda:changeme@host.docker.internal/pda - - GUNICORN_TIMEOUT=60 - - GUNICORN_WORKERS=2 - - GUNICORN_LOGLEVEL=DEBUG - - OFFLINE_MODE=False # True for offline, False for external resources diff --git a/docker-test/Dockerfile b/docker-test/Dockerfile deleted file mode 100644 index 577e120..0000000 --- a/docker-test/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -FROM debian:stretch-slim -LABEL maintainer="k@ndk.name" - -ENV LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 - -RUN apt-get update -y \ - && apt-get install -y --no-install-recommends apt-transport-https locales locales-all python3-pip python3-setuptools python3-dev curl libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev libffi-dev build-essential libmariadb-dev-compat \ - && curl -sL https://deb.nodesource.com/setup_10.x | bash - \ - && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && apt-get update -y \ - && apt-get install -y nodejs yarn \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* - -# We copy just the requirements.txt first to leverage Docker cache -COPY ./requirements.txt /app/requirements.txt - -WORKDIR /app -RUN pip3 install --upgrade pip -RUN pip3 install -r requirements.txt - -COPY . /app -COPY ./docker/entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/entrypoint.sh - -ENV FLASK_APP=powerdnsadmin/__init__.py -RUN yarn install --pure-lockfile --production \ - && yarn cache clean \ - && flask assets build - -COPY ./docker-test/wait-for-pdns.sh /opt -RUN chmod u+x /opt/wait-for-pdns.sh -CMD ["/opt/wait-for-pdns.sh", "/usr/local/bin/pytest","--capture=no","-vv"] diff --git a/docker-test/Dockerfile.pdns b/docker-test/Dockerfile.pdns deleted file mode 100644 index c234c98..0000000 --- a/docker-test/Dockerfile.pdns +++ /dev/null @@ -1,13 +0,0 @@ -FROM ubuntu:latest - -RUN apt-get update && apt-get install -y pdns-backend-sqlite3 pdns-server sqlite3 - -COPY ./docker-test/pdns.sqlite.sql /data/pdns.sql -ADD ./docker-test/start.sh /data/ - -RUN rm -f /etc/powerdns/pdns.d/pdns.simplebind.conf -RUN rm -f /etc/powerdns/pdns.d/bind.conf - -RUN chmod +x /data/start.sh && mkdir -p /var/empty/var/run - -CMD /data/start.sh diff --git a/docker-test/env b/docker-test/env deleted file mode 100644 index 30d9bb3..0000000 --- a/docker-test/env +++ /dev/null @@ -1,5 +0,0 @@ -PDNS_PROTO=http -PDNS_PORT=8081 -PDNS_HOST=pdns-server -PDNS_API_KEY=changeme -PDNS_WEBSERVER_ALLOW_FROM=0.0.0.0/0 \ No newline at end of file diff --git a/docker-test/start.sh b/docker-test/start.sh deleted file mode 100644 index 9a66017..0000000 --- a/docker-test/start.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env sh - -if [ -z ${PDNS_API_KEY+x} ]; then - API_KEY=changeme -fi - -if [ -z ${PDNS_PORT+x} ]; then - WEB_PORT=8081 -fi - -# Import schema structure -if [ -e "/data/pdns.sql" ]; then - rm /data/pdns.db - cat /data/pdns.sql | sqlite3 /data/pdns.db - rm /data/pdns.sql - echo "Imported schema structure" -fi - -chown -R pdns:pdns /data/ - -/usr/sbin/pdns_server \ - --launch=gsqlite3 --gsqlite3-database=/data/pdns.db \ - --webserver=yes --webserver-address=0.0.0.0 --webserver-port=${PDNS_PORT} \ - --api=yes --api-key=$PDNS_API_KEY --webserver-allow-from=${PDNS_WEBSERVER_ALLOW_FROM} diff --git a/docker-test/wait-for-pdns.sh b/docker-test/wait-for-pdns.sh deleted file mode 100644 index d4c301b..0000000 --- a/docker-test/wait-for-pdns.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -set -e - -CMD="$1" -shift -CMD_ARGS="$@" - -LOOPS=10 -until curl -H "X-API-Key: ${PDNS_API_KEY}" "${PDNS_PROTO}://${PDNS_HOST}:${PDNS_PORT}/api/v1/servers"; do - >&2 echo "PDNS is unavailable - sleeping" - sleep 1 - if [ $LOOPS -eq 10 ] - then - break - fi -done - -sleep 5 - ->&2 echo "PDNS is up - executing command" -exec $CMD $CMD_ARGS diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 5296e02..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,95 +0,0 @@ -FROM alpine:3.13 AS builder -LABEL maintainer="k@ndk.name" - -ARG BUILD_DEPENDENCIES="build-base \ - libffi-dev \ - libxml2-dev \ - mariadb-connector-c-dev \ - openldap-dev \ - python3-dev \ - xmlsec-dev \ - yarn \ - cargo" - -ENV LC_ALL=en_US.UTF-8 \ - LANG=en_US.UTF-8 \ - LANGUAGE=en_US.UTF-8 \ - FLASK_APP=/build/powerdnsadmin/__init__.py - -# Get dependencies -# py3-pip should not belong to BUILD_DEPENDENCIES. Otherwise, when we remove -# them with "apk del" at the end of build stage, the python requests module -# will be removed as well - (Tested with alpine:3.12 and python 3.8.5). -RUN apk add --no-cache ${BUILD_DEPENDENCIES} && \ - apk add --no-cache py3-pip - -WORKDIR /build - -# We copy just the requirements.txt first to leverage Docker cache -COPY ./requirements.txt /build/requirements.txt - -# Get application dependencies -RUN pip install --upgrade pip && \ - pip install -r requirements.txt - -# Add sources -COPY . /build - -# Prepare assets -RUN yarn install --pure-lockfile --production && \ - yarn cache clean && \ - sed -i -r -e "s|'cssmin',\s?'cssrewrite'|'cssmin'|g" /build/powerdnsadmin/assets.py && \ - flask assets build - -RUN mv /build/powerdnsadmin/static /tmp/static && \ - mkdir /build/powerdnsadmin/static && \ - cp -r /tmp/static/generated /build/powerdnsadmin/static && \ - cp -r /tmp/static/assets /build/powerdnsadmin/static && \ - cp -r /tmp/static/img /build/powerdnsadmin/static && \ - find /tmp/static/node_modules -name 'fonts' -exec cp -r {} /build/powerdnsadmin/static \; && \ - find /tmp/static/node_modules/icheck/skins/square -name '*.png' -exec cp {} /build/powerdnsadmin/static/generated \; - -RUN { \ - echo "from flask_assets import Environment"; \ - echo "assets = Environment()"; \ - echo "assets.register('js_login', 'generated/login.js')"; \ - echo "assets.register('js_validation', 'generated/validation.js')"; \ - echo "assets.register('css_login', 'generated/login.css')"; \ - echo "assets.register('js_main', 'generated/main.js')"; \ - echo "assets.register('css_main', 'generated/main.css')"; \ - } > /build/powerdnsadmin/assets.py - -# Move application -RUN mkdir -p /app && \ - cp -r /build/migrations/ /build/powerdnsadmin/ /build/run.py /app && \ - mkdir -p /app/configs && \ - cp -r /build/configs/docker_config.py /app/configs - -# Build image -FROM alpine:3.13 - -ENV FLASK_APP=/app/powerdnsadmin/__init__.py \ - USER=pda - -RUN apk add --no-cache mariadb-connector-c postgresql-client py3-gunicorn py3-psycopg2 xmlsec tzdata libcap && \ - addgroup -S ${USER} && \ - adduser -S -D -G ${USER} ${USER} && \ - mkdir /data && \ - chown ${USER}:${USER} /data && \ - setcap cap_net_bind_service=+ep $(readlink -f /usr/bin/python3) && \ - apk del libcap - -COPY --from=builder /usr/bin/flask /usr/bin/ -COPY --from=builder /usr/lib/python3.8/site-packages /usr/lib/python3.8/site-packages/ -COPY --from=builder --chown=root:${USER} /app /app/ -COPY ./docker/entrypoint.sh /usr/bin/ - -WORKDIR /app -RUN chown ${USER}:${USER} ./configs /app && \ - cat ./powerdnsadmin/default_config.py ./configs/docker_config.py > ./powerdnsadmin/docker_config.py - -EXPOSE 80/tcp -USER ${USER} -HEALTHCHECK CMD ["wget","--output-document=-","--quiet","--tries=1","http://127.0.0.1/"] -ENTRYPOINT ["entrypoint.sh"] -CMD ["gunicorn","powerdnsadmin:create_app()"] diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile new file mode 100644 index 0000000..eede6a9 --- /dev/null +++ b/docker/alpine/Dockerfile @@ -0,0 +1,66 @@ +ARG DISTRO=alpine +ARG DISTRO_TAG=3.14 + +FROM ${DISTRO}:${DISTRO_TAG} +LABEL maintainer="matt@azorian.solutions" + +# Set additional container environment variables +ENV LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US.UTF-8 \ + PDA_UID=${PDA_UID:-1000} \ + PDA_USER=${PDA_USER:-pda} \ + PDA_GID=${PDA_GID:-1000} \ + PDA_GROUP=${PDA_GROUP:-pda} \ + FLASK_APP=${FLASK_APP:-/srv/app/run.py} + +# Perform the following tasks: +# - Install required APK packages per the required dependencies +# - Upgrade Python pip package using pip3 +RUN apk update \ + && apk add --no-cache bash grep curl build-base tzdata libcap libffi-dev libxml2-dev xmlsec-dev mariadb-connector-c-dev \ + openldap-dev python3-dev py3-pip py3-gunicorn py3-psycopg2 yarn cargo mysql-client postgresql-client sqlite \ + && pip install --upgrade pip + +# Copy Python pip requirements file for early installation which leverages the image caching mechanisms +COPY /requirements.txt /srv/app/requirements.txt + +# Change the working directory which may affect the build contexts of following commands +WORKDIR /srv/app + +# Install the required Python pip packages as defined in the ./requirements.txt file +RUN pip install -r requirements.txt + +# Copy all project files into the container's /app directory +COPY . /srv/app + +# Perform the following tasks: +# - Create system group and user as configured in the PDA_GROUP and PDA_USER environment variables +# - Copy additional automation scripts to their appropriate locations +# - Set permissions on automation scripts +# - Install Yarn and clean the Yarn cache +# - Build Flask assets +# - Apply netcaps and cleanup APK packages +RUN addgroup -S --gid $PDA_GID $PDA_GROUP \ + && adduser -S -D --uid $PDA_UID -G $PDA_GROUP $PDA_USER \ + # XXX: Is this setcap still needed? + && setcap cap_net_bind_service=+ep $(readlink -f /usr/bin/python3) \ + && apk del libcap \ + && yarn install --pure-lockfile --production \ + && yarn cache clean \ + && flask assets build + +# Set the system user and group for the primary container process +USER "${PDA_USER}:${PDA_GROUP}" + +# Define the app directory as a volume +VOLUME /srv/app + +# Expose the configured service port +EXPOSE 80/TCP + +# Set the entrypoint script of the container +ENTRYPOINT ["/bin/bash", "/srv/app/docker/shared/entrypoint"] + +# Set the default command of the container to be ran following the entrypoint script +CMD ["/bin/sh", "-c", "/srv/app/run.py"] diff --git a/docker/bin/build-image b/docker/bin/build-image new file mode 100755 index 0000000..e3e25ef --- /dev/null +++ b/docker/bin/build-image @@ -0,0 +1,49 @@ +#!/bin/bash + +script_path=$(readlink -f "$0") +bin_path=$(dirname "$script_path") +app_path=$(dirname $(dirname "$bin_path")) +docker_path=$app_path/docker +docker_repo_config_path=$docker_path/repo.cfg +docker_distro=alpine +docker_distro_tag=3.14 +docker_tag=latest + +if [ ! -z "$1" ]; +then + docker_distro=$1 +fi + +if [ ! -z "$2" ]; +then + docker_distro_tag=$2 +fi + +if [ ! -z "$3" ]; +then + docker_tag=$3 +fi + +if [ -f "$docker_repo_config_path" ]; +then + docker_repo=`cat $docker_repo_config_path` + + if [ -f "$docker_path/$docker_distro/Dockerfile-$docker_tag" ]; + then + docker_file=$docker_path/$docker_distro/Dockerfile-$docker_tag + elif [ -f "$docker_path/$docker_distro/Dockerfile-$docker_distro-$docker_distro_tag" ]; + then + docker_file=$docker_path/$docker_distro/Dockerfile-$docker_distro-$docker_distro_tag + elif [ -f "$docker_path/$docker_distro/Dockerfile-$docker_distro" ]; + then + docker_file=$docker_path/$docker_distro/Dockerfile-$docker_distro + else + docker_file=$docker_path/$docker_distro/Dockerfile + fi + + docker build --force-rm -f $docker_file -t $docker_repo:$docker_tag --build-arg DISTRO=$docker_distro --build-arg DISTRO_TAG=$docker_distro_tag $app_path + +else + echo "Could not load Docker registry path from $docker_repo_config_path" + exit 1 +fi diff --git a/docker/bin/create-release b/docker/bin/create-release new file mode 100644 index 0000000..a9bf588 --- /dev/null +++ b/docker/bin/create-release @@ -0,0 +1 @@ +#!/bin/bash diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile new file mode 100644 index 0000000..1fe6f94 --- /dev/null +++ b/docker/debian/Dockerfile @@ -0,0 +1,77 @@ +ARG DISTRO=debian +ARG DISTRO_TAG=11.1-slim + +FROM ${DISTRO}:${DISTRO_TAG} +LABEL maintainer="matt@azorian.solutions" + +# Set additional container environment variables +ENV LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US.UTF-8 \ + PDA_UID=${PDA_UID:-1000} \ + PDA_USER=${PDA_USER:-pda} \ + PDA_GID=${PDA_GID:-1000} \ + PDA_GROUP=${PDA_GROUP:-pda} \ + FLASK_APP=${FLASK_APP:-/srv/app/run.py} + +# Perform the following tasks: +# - Install required APT packages per the required dependencies +# - Download and execute NodeSource setup package +# - Download and add Yarn public GPG key to APT +# - Add Yarn repository to the APT configuration +# - Update APT cache +# - Install NodeJS and Yarn APT packages +# - Clean APT caches and dynamic lists +# - Upgrade Python pip package using pip3 +RUN apt update -y \ + && apt install -y --no-install-recommends apt-transport-https locales locales-all python3-pip python3-setuptools \ + python3-dev curl libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxmlsec1 libxslt1-dev libxmlsec1-dev xmlsec1 \ + libffi-dev build-essential libmariadb-dev-compat pkg-config default-mysql-client postgresql-client sqlite3 \ + netcat-openbsd \ + && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && apt update -y \ + && apt install -y nodejs yarn \ + && apt clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && pip3 install --upgrade pip + +# Copy Python pip requirements file for early installation which leverages the image caching mechanisms +COPY /requirements.txt /srv/app/requirements.txt + +# Change the working directory which may affect the build contexts of following commands +WORKDIR /srv/app + +# Install the required Python pip packages as defined in the ./requirements.txt file +RUN pip3 install -r requirements.txt + +# Copy all project files into the container's /srv/app directory +COPY . /srv/app + +# Perform the following tasks: +# - Copy additional automation scripts to their appropriate locations +# - Set permissions on automation scripts +# - Install Yarn and clean the Yarn cache +# - Build Flask assets +RUN addgroup --system --gid $PDA_GID $PDA_GROUP \ + && adduser --system --disabled-login --no-create-home --home /tmp --shell /bin/false --uid $PDA_UID \ + --ingroup $PDA_GROUP $PDA_USER 2>/dev/null \ + && yarn install --pure-lockfile --production \ + && yarn cache clean \ + && flask assets build + +# Set the system user and group for the primary container process +USER "${PDA_USER}:${PDA_GROUP}" + +# Define the app directory as a volume +VOLUME /srv/app + +# Expose the configured service port +EXPOSE 80/TCP + +# Set the entrypoint script of the container +ENTRYPOINT ["/bin/bash", "/srv/app/docker/shared/entrypoint"] + +# Set the default command of the container to be ran following the entrypoint script +CMD ["/bin/sh", "-c", "/srv/app/run.py"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100755 index b1d0c24..0000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -set -euo pipefail -cd /app - -GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}" -GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}" -GUNICORN_LOGLEVEL="${GUNICORN_LOGLEVEL:-info}" -BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0:80}" - -GUNICORN_ARGS="-t ${GUNICORN_TIMEOUT} --workers ${GUNICORN_WORKERS} --bind ${BIND_ADDRESS} --log-level ${GUNICORN_LOGLEVEL}" -if [ "$1" == gunicorn ]; then - /bin/sh -c "flask db upgrade" - exec "$@" $GUNICORN_ARGS - -else - exec "$@" -fi diff --git a/docker/repo.cfg b/docker/repo.cfg new file mode 100644 index 0000000..9289d7d --- /dev/null +++ b/docker/repo.cfg @@ -0,0 +1 @@ +ngoduykhanh/powerdns-admin \ No newline at end of file diff --git a/docker/shared/entrypoint b/docker/shared/entrypoint new file mode 100755 index 0000000..c68f540 --- /dev/null +++ b/docker/shared/entrypoint @@ -0,0 +1,238 @@ +#!/bin/bash + +script_path=$(readlink -f "$0") +app_path=$(dirname $(dirname $(dirname "$script_path"))) +docker_path=$app_path/docker +auto_init_path=$docker_path/.autoinit + +######################################################################################################################## +# START CONFIGURATION +######################################################################################################################## + +# The container interface IP mask to bind both gunicorn and the built-in web server to +PDA_BIND_ADDRESS=${PDA_BIND_ADDRESS:-0.0.0.0} + +# The container port to bind both gunicorn and the built-in web server to +PDA_BIND_PORT=${PDA_BIND_PORT:-80} + +# The debug mode setting of the app +PDA_DEBUG=${PDA_DEBUG:-false} + +# Whether to automatically convert environment variables beginning with PDA_ and ending with _FILE +PDA_LOAD_ENV_FILES=${PDA_LOAD_ENV_FILES:-true} + +# Whether the automatic first-time initialization feature is enabled +PDA_AUTOINIT_ENABLED=${PDA_AUTOINIT_ENABLED:-false} + +# Whether to force execution of the first-time initialization feature +PDA_AUTOINIT_FORCE=${PDA_AUTOINIT_FORCE:-false} + +# Whether to delay container startup until the configured MySQL server is online +PDA_CHECK_MYSQL_ENABLED=${PDA_CHECK_MYSQL_ENABLED:-false} + +# How many seconds to wait after a MySQL connection attempt failure before trying again +PDA_CHECK_MYSQL_FAIL_DELAY=${PDA_CHECK_MYSQL_FAIL_DELAY:-2} + +# How many seconds to wait after a successful MySQL connection attempt before proceeding to the next step +PDA_CHECK_MYSQL_SUCCESS_DELAY=${PDA_CHECK_MYSQL_SUCCESS_DELAY:-0} + +# How many MySQL connection attempts should be made before halting container execution +PDA_CHECK_MYSQL_ATTEMPTS=${PDA_CHECK_MYSQL_ATTEMPTS:-30} + +# Whether to delay container startup until the configured PDNS API key passes authorization to the server +PDA_CHECK_API_ENABLED=${PDA_CHECK_API_ENABLED:-false} + +# How many seconds to wait after an API connection attempt failure before trying again +PDA_CHECK_API_FAIL_DELAY=${PDA_CHECK_API_FAIL_DELAY:-2} + +# How many seconds to wait after an API successful connection attempt before proceeding to the next step +PDA_CHECK_API_SUCCESS_DELAY=${PDA_CHECK_API_SUCCESS_DELAY:-0} + +# How many API connection attempts should be made before halting container execution +PDA_CHECK_API_ATTEMPTS=${PDA_CHECK_API_ATTEMPTS:-15} + +# Whether to run the app's Python test unit before starting the primary container process +PDA_CHECK_PYTEST_ENABLED=${PDA_CHECK_PYTEST_ENABLED:-false} + +# Whether to enable the use of gunicorn +PDA_GUNICORN_ENABLED=${PDA_GUNICORN_ENABLED:-false} + +# The request timeout in seconds for a gunicorn worker +PDA_GUNICORN_TIMEOUT=${PDA_GUNICORN_TIMEOUT:-120} + +# The total number of gunicorn worker threads to spawn +PDA_GUNICORN_WORKERS=${PDA_GUNICORN_WORKERS:-4} + +# The log output level of the gunicorn process +PDA_GUNICORN_LOGLEVEL=${PDA_GUNICORN_LOGLEVEL:-info} + +# The additional gunicorn arguments that will be appended to the startup command when gunicorn is enabled +PDA_GUNICORN_ARGS=${PDA_GUNICORN_ARGS:-"-t ${PDA_GUNICORN_TIMEOUT} -w ${PDA_GUNICORN_WORKERS} \ +--log-level ${PDA_GUNICORN_LOGLEVEL} --bind ${PDA_BIND_ADDRESS}:${PDA_BIND_PORT}"} + +# The API key for the associated PowerDNS Authoritative server API +PDA_PDNS_API_KEY=${PDA_PDNS_API_KEY:-CHANGE_ME!} + +# The API version of the associated PowerDNS Authoritative server API +PDA_PDNS_API_VERSION=${PDA_PDNS_API_VERSION:-4.5.2} + +# The HTTP protocol for the associated PowerDNS Authoritative server API +PDA_PDNS_API_PROTO=${PDA_PDNS_API_PROTO:-http} + +# The hostname or IP address of the associated PowerDNS Authoritative server API +PDA_PDNS_API_HOST=${PDA_PDNS_API_HOST:-127.0.0.1} + +# The port of the associated PowerDNS Authoritative server API +PDA_PDNS_API_PORT=${PDA_PDNS_API_PORT:-8081} + +# The username to use for automatic admin account setup on startup +PDA_ADMIN_USER_USERNAME=${PDA_ADMIN_USER_USERNAME:-} + +# The password to use for automatic admin account setup on startup +PDA_ADMIN_USER_PASSWORD=${PDA_ADMIN_USER_PASSWORD:-} + +######################################################################################################################## +# END CONFIGURATION +######################################################################################################################## + +convert_file_vars() { + for line in $(env); do + len=$(($(echo $line | wc -c) - 1)) + key=$(echo $line | cut -c1-$(echo $line | grep -aob '=' | grep -oE '[0-9]+')) + value=$(echo $line | cut -c$(($(echo $line | grep -aob '=' | grep -oE '[0-9]+') + 2))-$len) + if [[ $key == PDA_* && $key == *_FILE && -n "$value" && -f "$value" ]]; then + len=$(($(echo $key | wc -c) - 1)) + new_key=${key:0:$((len - 5))} + file_value=$(cat "$value") + export "$new_key"="$file_value" + unset "$key" + fi + done +} + +auto_init() { + # Determine if the first run file exists and abort if so + if ([ ! -n "$PDA_AUTOINIT_FORCE" ] || [ ! "$PDA_AUTOINIT_FORCE" = "true" ]) && [ -f "$auto_init_path" ]; then + echo "Automatic initialization will abort as a first run file exists at $auto_init_path" + return + fi + + # Assume MySQL database if the SQLA_DB_HOST environment variable has been set, otherwise assume SQLite + if [ -n "$PDAC_SQLA_DB_HOST" ] && [ -n "$PDAC_SQLA_DB_USER" ] && [ -n "$PDAC_SQLA_DB_PASSWORD" ] \ + && [ -n "$PDAC_SQLA_DB_PASSWORD" ] && [ -n "$PDAC_SQLA_DB_NAME" ]; then + table=tsigkeys + sql=$(printf "SHOW TABLES LIKE '%s'" "$table") + if [[ -z $(mysql -h $PDAC_SQLA_DB_HOST -u $PDAC_SQLA_DB_USER -p$PDAC_SQLA_DB_PASSWORD -e "$sql" $PDAC_SQLA_DB_NAME) ]]; then + echo "The table $table does not exist so the base pdns schema will be installed." + mysql_result=$(mysql -h $PDAC_SQLA_DB_HOST -u $PDAC_SQLA_DB_USER -p$PDAC_SQLA_DB_PASSWORD $PDAC_SQLA_DB_NAME < /srv/app/docker/shared/pdns-schema-mysql.sql) + else + echo "The table $table does exist so no further action will be taken." + fi + fi + + if [ ! -f "/srv/app/pdns.db" ]; then + cat /srv/app/docker/shared/pdns-schema-sqlite.sql | sqlite3 /srv/app/pdns.db + fi + + touch $auto_init_path; +} + +verify_mysql_ready() { + local host=$1 + local port=$2 + local retry_executed=1 + + while ! nc -z $host $port >& /dev/null; do + # The last connection test to the MySQL server failed at this point + + # If the remaining retry counter falls to zero, exit the connection test cycle + if [ $retry_executed -ge $PDA_CHECK_MYSQL_ATTEMPTS ]; then + echo "The maximum number ($PDA_CHECK_MYSQL_ATTEMPTS) of TCP connection tests have been executed without success. This container will now exit." + exit 1 + else + echo "MySQL server is offline. The TCP connection test cycle number $retry_executed has failed to $host:$port. Waiting for $PDA_CHECK_MYSQL_FAIL_DELAY seconds..." + fi + + # Delay execution for the configured MySQL fail delay + sleep $PDA_CHECK_MYSQL_FAIL_DELAY + + # Increment the retry execution counter + retry_executed=$((retry_executed + 1)) + done + + echo "MySQL server is online after $retry_executed check(s). Delaying execution for $PDA_CHECK_MYSQL_SUCCESS_DELAY seconds..." + + sleep $PDA_CHECK_MYSQL_SUCCESS_DELAY +} + +verify_api_ready() { + local retry_executed=1 + local api_uri="$PDA_PDNS_PROTO://$PDA_PDNS_HOST:$PDA_PDNS_PORT/api/v1/servers" + local auth_header="X-API-Key: $PDA_PDNS_API_KEY" + + while [ $(curl -s -o /dev/null -w "%{http_code}" -H "$auth_header" "$api_uri") -ne "200" ]; do + # The last connection test to the API server failed at this point + + # If the remaining retry counter falls to zero, exit the connection test cycle + if [ $retry_executed -ge $PDA_CHECK_API_ATTEMPTS ]; then + echo "The maximum number ($PDA_CHECK_API_ATTEMPTS) of API server HTTP connection tests have been executed without success. This container will now exit." + exit 1 + else + echo "PowerDNS API server is offline. The HTTP connection test cycle number $retry_executed has failed to $api_uri. Waiting for $PDA_CHECK_API_FAIL_DELAY seconds..." + fi + + # Delay execution for the configured API fail delay + sleep $PDA_CHECK_API_FAIL_DELAY + + # Increment the retry execution counter + retry_executed=$((retry_executed + 1)) + done + + echo "PowerDNS API server is online after $retry_executed check(s). Delaying execution for ${PDA_CHECK_API_SUCCESS_DELAY} seconds..." + + sleep $PDA_CHECK_API_SUCCESS_DELAY +} + +# Automatically load any environment variables that begin with "PDA_" and end with "_FILE" if this feature is enabled +if [ "$PDA_LOAD_ENV_FILES" = "true" ]; then + convert_file_vars +fi + +# Verify that the configured MySQL server is ready for connections +if [ "$PDA_CHECK_MYSQL_ENABLED" = "true" ] && [ -n "$PDAC_SQLA_DB_HOST" ]; then + verify_mysql_ready "$PDAC_SQLA_DB_HOST" "${PDAC_SQLA_DB_PORT:-3306}" +fi + +# Execute first-run automatic initialization +# - Setup the base PowerDNS Authoritative server database schema if this is the first run if this feature is enabled +if [ "$PDA_AUTOINIT_ENABLED" = "true" ] || [ "$PDA_AUTOINIT_FORCE" = "true" ]; then + auto_init +fi + +# Verify that the configured PowerDNS name server API is ready for connections if this feature is enabled +if [ "$PDA_CHECK_API_ENABLED" = "true" ] && [ -n "$PDA_PDNS_HOST" ]; then + verify_api_ready +fi + +echo "Proceeding with app initialization..." +cd /srv/app + +# Execute any pending database upgrades via Flask +flask db upgrade + +if [ "$FLASK_ENV" = "development" ]; then + flask assets build +fi + +# Run Python test units if this feature is enabled +if [ "$PDA_CHECK_PYTEST_ENABLED" = "true" ]; then + pytest +fi + +# If gunicorn is configured for use then execute the gunicorn command with the configuration arguments and container +# command appended. Otherwise, just execute the container command. +if [ "$PDA_GUNICORN_ENABLED" = "true" ]; then + exec "gunicorn" $PDA_GUNICORN_ARGS "$@" +else + exec "$@" +fi diff --git a/docker/shared/pdns-schema-mysql.sql b/docker/shared/pdns-schema-mysql.sql new file mode 100644 index 0000000..9b1bdd1 --- /dev/null +++ b/docker/shared/pdns-schema-mysql.sql @@ -0,0 +1,89 @@ +CREATE TABLE domains ( + id INT AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + master VARCHAR(128) DEFAULT NULL, + last_check INT DEFAULT NULL, + type VARCHAR(6) NOT NULL, + notified_serial INT UNSIGNED DEFAULT NULL, + account VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE UNIQUE INDEX name_index ON domains(name); + + +CREATE TABLE records ( + id BIGINT AUTO_INCREMENT, + domain_id INT DEFAULT NULL, + name VARCHAR(255) DEFAULT NULL, + type VARCHAR(10) DEFAULT NULL, + content VARCHAR(64000) DEFAULT NULL, + ttl INT DEFAULT NULL, + prio INT DEFAULT NULL, + disabled TINYINT(1) DEFAULT 0, + ordername VARCHAR(255) BINARY DEFAULT NULL, + auth TINYINT(1) DEFAULT 1, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX nametype_index ON records(name,type); +CREATE INDEX domain_id ON records(domain_id); +CREATE INDEX ordername ON records (ordername); + + +CREATE TABLE supermasters ( + ip VARCHAR(64) NOT NULL, + nameserver VARCHAR(255) NOT NULL, + account VARCHAR(40) CHARACTER SET 'utf8' NOT NULL, + PRIMARY KEY (ip, nameserver) +) Engine=InnoDB CHARACTER SET 'latin1'; + + +CREATE TABLE comments ( + id INT AUTO_INCREMENT, + domain_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(10) NOT NULL, + modified_at INT NOT NULL, + account VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL, + comment TEXT CHARACTER SET 'utf8' NOT NULL, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX comments_name_type_idx ON comments (name, type); +CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); + + +CREATE TABLE domainmetadata ( + id INT AUTO_INCREMENT, + domain_id INT NOT NULL, + kind VARCHAR(32), + content TEXT, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind); + + +CREATE TABLE cryptokeys ( + id INT AUTO_INCREMENT, + domain_id INT NOT NULL, + flags INT NOT NULL, + active BOOL, + published BOOL DEFAULT 1, + content TEXT, + PRIMARY KEY(id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX domainidindex ON cryptokeys(domain_id); + + +CREATE TABLE tsigkeys ( + id INT AUTO_INCREMENT, + name VARCHAR(255), + algorithm VARCHAR(50), + secret VARCHAR(255), + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); \ No newline at end of file diff --git a/docker-test/pdns.sqlite.sql b/docker/shared/pdns-schema-sqlite.sql similarity index 97% rename from docker-test/pdns.sqlite.sql rename to docker/shared/pdns-schema-sqlite.sql index 4748a8d..c9e12a1 100644 --- a/docker-test/pdns.sqlite.sql +++ b/docker/shared/pdns-schema-sqlite.sql @@ -89,4 +89,4 @@ CREATE TABLE tsigkeys ( secret VARCHAR(255) ); -CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); +CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); \ No newline at end of file diff --git a/docker/stacks/local/mysql/docker-compose.yml b/docker/stacks/local/mysql/docker-compose.yml new file mode 100644 index 0000000..4f5fe7a --- /dev/null +++ b/docker/stacks/local/mysql/docker-compose.yml @@ -0,0 +1,104 @@ +version: "3.3" + +services: + + mysql: + image: mariadb:10.6 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - MYSQL_DATABASE=pda + - MYSQL_USER=pda + - MYSQL_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + + phpmyadmin: + image: phpmyadmin/phpmyadmin:5.1 + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PMA_HOST=mysql + - PMA_PORT=3306 + ports: + - 8082:80 + + powerdns-admin: + image: ngoduykhanh/powerdns-admin:latest + depends_on: [ 'mysql' ] + restart: unless-stopped + environment: + - PDA_BIND_ADDRESS=0.0.0.0 + - PDA_BIND_PORT=80 + - PDA_DEBUG=true + - PDA_LOAD_ENV_FILES=true + - PDA_AUTOINIT_ENABLED=true + - PDA_AUTOINIT_FORCE=false + - PDA_CHECK_MYSQL_ENABLED=true + - PDA_CHECK_MYSQL_FAIL_DELAY=2 + - PDA_CHECK_MYSQL_SUCCESS_DELAY=0 + - PDA_CHECK_MYSQL_ATTEMPTS=30 + - PDA_CHECK_API_ENABLED=true + - PDA_CHECK_API_FAIL_DELAY=2 + - PDA_CHECK_API_SUCCESS_DELAY=0 + - PDA_CHECK_API_ATTEMPTS=15 + - PDA_CHECK_PYTEST_ENABLED=false + - PDA_GUNICORN_ENABLED=false + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=2 + - PDA_GUNICORN_LOGLEVEL=debug + - PDA_PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr + - PDA_PDNS_VERSION=4.5.2 + - PDA_PDNS_PROTO=http + - PDA_PDNS_HOST=pdns + - PDA_PDNS_PORT=8081 + - PDA_ADMIN_USER_USERNAME=nocadmin + - PDA_ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=true + - PDAC_HSTS_ENABLED=false + - PDAC_OFFLINE_MODE=false + - PDAC_FILESYSTEM_SESSIONS_ENABLED=false + - PDAC_SQLA_DB_HOST=mysql + - PDAC_SQLA_DB_NAME=pda + - PDAC_SQLA_DB_USER=root + - PDAC_SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - PDAC_SQLA_DB_PORT=3306 + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=true + - FLASK_ENV=development + ports: + - "8080:80" + volumes: + - type: bind + source: ${PDA_LOCAL_PATH} + target: /srv/app + + pdns: + image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql + depends_on: ['powerdns-admin'] + restart: unless-stopped + environment: + - AS_MYSQL_CHECK_RETRY=10 + - AS_MYSQL_CHECK_INTERVAL=4 + - PDNS_launch=gmysql + - PDNS_gmysql_host=mysql + - PDNS_gmysql_port=3306 + - PDNS_gmysql_user=root + - PDNS_gmysql_password=qHZDZ26fPqqCfKZtWkQ9 + - PDNS_gmysql_dbname=pda + - PDNS_gmysql_dnssec=yes + - PDNS_default_soa_content=pdns noc.@ 0 10800 3600 604800 3600 + - PDNS_default_ttl=3600 + - PDNS_local_address=0.0.0.0 + - PDNS_local_port=53 + - PDNS_api=yes + - PDNS_api_key=scjfNRN4Ch2DBuhk24vr + - PDNS_webserver=yes + - PDNS_webserver_address=0.0.0.0 + - PDNS_webserver_allow_from=0.0.0.0/0 + - PDNS_any_to_tcp=yes + - PDNS_master=no + ports: + - "5053:53/tcp" + - "5053:53/udp" + - "8081:8081/tcp" \ No newline at end of file diff --git a/docker/stacks/local/sqlite/docker-compose.yml b/docker/stacks/local/sqlite/docker-compose.yml new file mode 100644 index 0000000..ce91a9d --- /dev/null +++ b/docker/stacks/local/sqlite/docker-compose.yml @@ -0,0 +1,96 @@ +version: "3.3" + +services: + + mysql: + image: mariadb:10.6 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - MYSQL_DATABASE=pda + - MYSQL_USER=pda + - MYSQL_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + + phpmyadmin: + image: phpmyadmin/phpmyadmin:5.1 + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PMA_HOST=mysql + - PMA_PORT=3306 + ports: + - 8082:80 + + powerdns-admin: + image: ngoduykhanh/powerdns-admin:latest + depends_on: [ 'mysql' ] + restart: unless-stopped + environment: + - PDA_BIND_ADDRESS=0.0.0.0 + - PDA_BIND_PORT=80 + - PDA_DEBUG=true + - PDA_LOAD_ENV_FILES=true + - PDA_AUTOINIT_ENABLED=true + - PDA_AUTOINIT_FORCE=false + - PDA_CHECK_API_ENABLED=true + - PDA_CHECK_API_FAIL_DELAY=2 + - PDA_CHECK_API_SUCCESS_DELAY=0 + - PDA_CHECK_API_ATTEMPTS=15 + - PDA_CHECK_PYTEST_ENABLED=false + - PDA_GUNICORN_ENABLED=false + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=2 + - PDA_GUNICORN_LOGLEVEL=debug + - PDA_PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr + - PDA_PDNS_VERSION=4.5.2 + - PDA_PDNS_PROTO=http + - PDA_PDNS_HOST=pdns + - PDA_PDNS_PORT=8081 + - PDA_ADMIN_USER_USERNAME=nocadmin + - PDA_ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=true + - PDAC_HSTS_ENABLED=false + - PDAC_OFFLINE_MODE=false + - PDAC_FILESYSTEM_SESSIONS_ENABLED=false + - PDAC_SQLALCHEMY_DATABASE_URI=sqlite:////srv/app/pdns.db + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=true + - FLASK_ENV=development + ports: + - "8080:80" + volumes: + - type: bind + source: ${PDA_LOCAL_PATH} + target: /srv/app + + pdns: + image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql + depends_on: ['powerdns-admin'] + restart: unless-stopped + environment: + - AS_MYSQL_CHECK_RETRY=10 + - AS_MYSQL_CHECK_INTERVAL=4 + - PDNS_launch=gmysql + - PDNS_gmysql_host=mysql + - PDNS_gmysql_port=3306 + - PDNS_gmysql_user=root + - PDNS_gmysql_password=qHZDZ26fPqqCfKZtWkQ9 + - PDNS_gmysql_dbname=pda + - PDNS_gmysql_dnssec=yes + - PDNS_default_soa_content=pdns noc.@ 0 10800 3600 604800 3600 + - PDNS_default_ttl=3600 + - PDNS_local_address=0.0.0.0 + - PDNS_local_port=53 + - PDNS_api=yes + - PDNS_api_key=scjfNRN4Ch2DBuhk24vr + - PDNS_webserver=yes + - PDNS_webserver_address=0.0.0.0 + - PDNS_webserver_allow_from=0.0.0.0/0 + - PDNS_any_to_tcp=yes + - PDNS_master=no + ports: + - "5053:53/tcp" + - "5053:53/udp" + - "8081:8081/tcp" \ No newline at end of file diff --git a/docker/stacks/production/mysql/docker-compose.yml b/docker/stacks/production/mysql/docker-compose.yml new file mode 100644 index 0000000..1beb727 --- /dev/null +++ b/docker/stacks/production/mysql/docker-compose.yml @@ -0,0 +1,40 @@ +version: "3.3" +services: + mysql: + image: mariadb:10.6 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - MYSQL_DATABASE=pda + - MYSQL_USER=pda + - MYSQL_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + powerdns-admin: + image: ngoduykhanh/powerdns-admin:0.2.4 + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PDA_CHECK_MYSQL_ENABLED=true + - PDA_CHECK_API_ENABLED=true + - PDA_GUNICORN_ENABLED=true + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=10 + - PDA_GUNICORN_LOGLEVEL=info + - PDA_PDNS_API_KEY=YOUR-PDNS-API-SERVER-API-KEY + - PDA_PDNS_VERSION=YOUR-PDNS-API-SERVER-VERSION + - PDA_PDNS_PROTO=YOUR-PDNS-API-SERVER-HTTP-PROTOCOL + - PDA_PDNS_HOST=YOUR-PDNS-API-SERVER-HOSTNAME + - PDA_PDNS_PORT=YOUR-PDNS-API-SERVER-PORT + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=false + - PDAC_HSTS_ENABLED=true + - PDAC_SQLA_DB_HOST=mysql + - PDAC_SQLA_DB_NAME=pda + - PDAC_SQLA_DB_USER=root + - PDAC_SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - PDAC_SQLA_DB_PORT=3306 + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=false + - FLASK_ENV=production + ports: + - "8080:80" \ No newline at end of file diff --git a/docker/stacks/production/sqlite/docker-compose.yml b/docker/stacks/production/sqlite/docker-compose.yml new file mode 100644 index 0000000..f846f20 --- /dev/null +++ b/docker/stacks/production/sqlite/docker-compose.yml @@ -0,0 +1,26 @@ +version: "3.3" +services: + powerdns-admin: + image: ngoduykhanh/powerdns-admin:0.2.4 + restart: unless-stopped + environment: + - PDA_CHECK_API_ENABLED=true + - PDA_GUNICORN_ENABLED=true + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=10 + - PDA_GUNICORN_LOGLEVEL=info + - PDA_PDNS_API_KEY=YOUR-PDNS-API-SERVER-API-KEY + - PDA_PDNS_VERSION=YOUR-PDNS-API-SERVER-VERSION + - PDA_PDNS_PROTO=YOUR-PDNS-API-SERVER-HTTP-PROTOCOL + - PDA_PDNS_HOST=YOUR-PDNS-API-SERVER-HOSTNAME + - PDA_PDNS_PORT=YOUR-PDNS-API-SERVER-PORT + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=false + - PDAC_HSTS_ENABLED=true + - PDAC_SQLALCHEMY_DATABASE_URI=sqlite:////srv/app/pdns.db + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=false + - FLASK_ENV=production + ports: + - "8080:80" \ No newline at end of file diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..65f62ed --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,142 @@ +# PowerDNS-Admin packaged by [Azorian Solutions](https://azorian.solutions) + +The PowerDNS-Admin is a simple web GUI for managing zone configurations of a PowerDNS Authoritative server. + +The PowerDNS-Admin app does NOT modify the PowerDNS Authoritative server database directly. Instead, it communicates with the PDNS server via the built-in HTTP API. + +The app does have a database for identity management, access control, and caching which can be hosted in either MySQL or SQLite. + +- [PowerDNS-Admin GitHub](https://github.com/ngoduykhanh/PowerDNS-Admin) +- [PowerDNS-Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) +- [PowerDNS-Admin Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki) + +## Quick reference + +- **Maintained by:** [Matt Scott](https://github.com/AzorianSolutions) +- **Github:** [https://github.com/AzorianSolutions](https://github.com/AzorianSolutions) +- **Website:** [https://azorian.solutions](https://azorian.solutions) + +## TL;DR + + docker run -d -p 8080:80 -e PDAC_SIGNUP_ENABLED=true ngoduykhanh/powerdns-nameserver + +## [Azorian Solutions](https://azorian.solutions) Docker image strategy + +The goal of creating this image and others alike is to provide a fairly uniform and turn-key implementation for a chosen set of products and solutions. By compiling the server binaries from source code, a greater chain of security is maintained by eliminating unnecessary trusts. This approach also helps assure support of specific features that may otherwise vary from distribution to distribution. A secondary goal of creating this image and others alike is to provide at least two Linux distribution options for any supported product or solution which is why you will often see tags for both Alpine Linux and Debian Linux. + +All documentation will be written with the assumption that you are already reasonably familiar with this ecosystem. This includes container concepts, the Docker ecosystem, and more specifically the product or solution that you are deploying. Simply put, I won't be fluffing the docs with content explaining every detail of what is presented. + +## Supported tags + +\* denotes an image that is planned but has not yet been released. + +### Alpine Linux + +- main, latest +- 0.2.4-alpine, alpine, 0.2.4 + +### Debian Linux + +- 0.2.4-debian, debian + +## Deploying this image + +### App configuration + +Configuration of the PowerDNS Admin app may be achieved through a mixture of two approaches. With either approach you choose, you will need to be aware of the various settings available for the app. + +[PowerDNS Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) + +#### Approach #1 + +You may pass app settings as environment variables to the container. These environment variables will be automatically inserted into the app.config object. Any environment variable that begins with PDA_ or PDAC_ and is entirely capitalized, will be inserted into the app.config object. + +**One key difference** between the two variable prefixes is that variables prefixed with PDAC_ will have the PDAC_ prefix removed before being inserted into the app.config object. + +For example, say you pass the environment variable PDA_BIND_ADDRESS with the value 0.0.0.0 to the container. + + PDA_BIND_ADDRESS=0.0.0.0 + +This will create a key named PDA_BIND_ADDRESS in app.config with the value of the environment variable value of 0.0.0.0. + +Similar to how secrets work for Docker Swarm containers, you may append _FILE to your environment variables to automatically load the contents of the given file path into an environment variable. + +**One key difference** here from Docker Swarm secrets is that you provide an absolute file path to any path of our choosing. Also, once the contents of the file are loaded, an environment variable of the same name without the trailing _FILE is created with the contents of the loaded file as the value. The original environment variable with the trailing _FILE is removed. + +The following example illustrates a scenario where you want to set the PDA_PDNS_API_KEY app setting in a secure fashion. You have the secret API key stored at /run/secrets/PDA_PDNS_API_KEY. + + PDA_PDNS_API_KEY_FILE=/run/secrets/PDA_PDNS_API_KEY + +This would be the equivalent of running: + + export PDA_PDNS_API_KEY=CONTENTS_OF_SECRET_FILE + +#### Approach #2 + +With this approach, you may create traditional PowerDNS Admin config files and map them to an accessible location inside the container. Then you can pass an environment variable to indicate where to load the file from. + +For example, say you have mapped a python configuration file to /srv/app/configs/my-config.py in the container. You would add the following environment variable to your deployment configuration: + + FLASK_CONF=/srv/app/configs/my-config.py + +**!!! NOTICE !!!** - There is a very specific order in which different sources are loaded. Take careful notice to avoid confusion! + +- The config file **/srv/app/configs/default.py** is loaded. +- The **FLASK_CONF** environment variable is loaded if configured. +- Any instance specific configuration provided via code instantiation is loaded. +- Settings are loaded individually from any environment variables present. +- If all required MySQL settings are present in the app.config object and no SQLALCHEMY_DATABASE_URI app setting has been configured, the app.config setting will be automatically created from the provided MySQL settings. + - Otherwise, the SQLALCHEMY_DATABASE_URI app setting is set to "sqlite:////srv/app/pdns.db" + +### Deploy with Docker Run + +To run a simple container on Docker with this image, execute the following Docker command; + + docker run -d -p 8080:80 -e PDAC_SIGNUP_ENABLED=true ngoduykhanh/powerdns-nameserver + +### Deploy with Docker Compose + +To run this image using Docker Compose, create a YAML file in a place of your choosing and add the following contents to it; + + version: "3.3" + services: + powerdns-admin: + image: ngoduykhanh/powerdns-admin + restart: unless-stopped + environment: + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + ports: + - "8080:80" + +Then execute the following Docker Compose command; + + docker-compose -f /path/to/yaml/file.yml up + +**!!! NOTICE !!!** - Make sure to set the environment variable `PDAC_SECRET_KEY` to a long random string ([see here for additional information](https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY)) + +## Building this image + +If you want to build this image yourself, you can easily do so using the **docker/bin/build-image** command I have included. + +The build-image command has the following parameter format; + + build-image IMAGE_TAG_NAME APP_VERSION DISTRO_NAME DISTRO_TAG + +So for example, to build the PowerDNS Admin app version 0.2.3 on Alpine Linux 3.14, you would execute the following shell command: + + build-image 0.2.3-alpine-3.14 0.2.3 alpine 3.14 + +The build-image command assumes the following parameter defaults; + +- Image Tag Name: latest +- App Version: 0.2.4 +- Distro Name: alpine +- Distro Tag: 3.14 + +This means that running the build-image command with no parameters would be the equivalent of executing the following shell command: + + build-image latest 0.2.4 alpine 3.14 + +When the image is tagged during compilation, the repository portion of the image tag is derived from the contents of the docker/repo.cfg file and the tag from the first parameter provided to the build-image command. + diff --git a/docs/settings.md b/docs/settings.md new file mode 100644 index 0000000..e79a86d --- /dev/null +++ b/docs/settings.md @@ -0,0 +1,430 @@ +# PowerDNS-Admin Settings + +The app has a plethora of settings that may be configured through a number of methods. Unfortunately, at the current state the app does not use a completely uniform naming scheme across configuration methods. + +Don't worry though! A strong standard is quickly being implemented with full backwards compatibility so this problem should disappear quickly. + +Let's break the application settings keys down into two primary categories, docker only and app settings. Below you will find a detailed description of each setting and how to use it. + +## Docker Only Settings + +\+ denotes a setting that is planned but not yet implemented. + +#### PDA_UID = 1000 + +This setting controls what user ID is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +#### PDA_USER = pda + +This setting controls what username is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +#### PDA_GID = 1000 + +This setting controls what group ID is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +#### PDA_GROUP = pda + +This setting controls what group name is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +#### PDA_LOCAL_PATH + +This setting controls with path on the Docker host machine will be used as the project root for local development. + +## App Settings +\* denotes a legacy setting key that will be deprecated. + +\+ denotes a setting that is planned but not yet implemented. + +**!COMPUTED!** denotes a default setting value that is computed dynamically at the time it is set. + +#### PDA_BIND_ADDRESS = 0.0.0.0 +The container interface IP mask to bind both gunicorn and the built-in web server to. + +#### PDA_BIND_PORT = 80 +The container port to bind both gunicorn and the built-in web server to. + +#### PDA_DEBUG = false +The debug mode setting of the app. + +#### PDA_LOAD_ENV_FILES = true +Whether to automatically convert environment variables beginning with PDA_ and ending with _FILE. + +#### PDA_AUTOINIT_ENABLED = true +Whether the automatic first-time initialization feature is enabled. + +#### PDA_AUTOINIT_FORCE = false +Whether to force execution of the first-time initialization feature. + +#### PDA_CHECK_MYSQL_ENABLED = false +Whether to delay container startup until the configured MySQL server is online. + +#### PDA_CHECK_MYSQL_FAIL_DELAY = 2 +How many seconds to wait after a MySQL connection attempt failure before trying again. + +#### PDA_CHECK_MYSQL_SUCCESS_DELAY = 0 +How many seconds to wait after a successful MySQL connection attempt before proceeding to the next step. + +#### PDA_CHECK_MYSQL_ATTEMPTS = 30 +How many MySQL connection attempts should be made before halting container execution. + +#### PDA_CHECK_API_ENABLED = false +Whether to delay container startup until the configured PDNS API key passes authorization to the server. + +#### PDA_CHECK_API_FAIL_DELAY = 2 +How many seconds to wait after an API connection attempt failure before trying again. + +#### PDA_CHECK_API_SUCCESS_DELAY = 0 +How many seconds to wait after an API successful connection attempt before proceeding to the next step. + +#### PDA_CHECK_API_ATTEMPTS = 15 +How many API connection attempts should be made before halting container execution. + +#### PDA_CHECK_PYTEST_ENABLED = false +Whether to run the app's Python test unit before starting the primary container process. + +#### PDA_GUNICORN_ENABLED = false +Whether to enable the use of gunicorn. + +#### PDA_GUNICORN_TIMEOUT = 120 +The request timeout in seconds for a gunicorn worker. + +#### PDA_GUNICORN_WORKERS = 4 +The total number of gunicorn worker threads to spawn. + +#### PDA_GUNICORN_LOGLEVEL = info +The log output level of the gunicorn process. + +#### PDA_GUNICORN_ARGS = !COMPUTED! +The additional gunicorn arguments that will be appended to the startup command when gunicorn is enabled. + +#### PDA_PDNS_API_KEY = CHANGE_ME! +The API key for the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_VERSION = 4.5.2 +The API version of the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_PROTO = http +The HTTP protocol for the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_HOST = 127.0.0.1 +The hostname or IP address of the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_PORT = 8081 +The port of the associated PowerDNS Authoritative server API. + +#### + PDA_ADMIN_USER_USERNAME +The username to use for automatic admin account setup on startup. + +#### + PDA_ADMIN_USER_PASSWORD +The password to use for automatic admin account setup on startup. + +#### * FLASK_ENV = production +The Flask app environment classification. + +**!!! NOTICE !!!** - Setting this to development carries implicit behaviors such as automatically setting the FLASK_DEBUG setting to true. + +#### * FLASK_DEBUG = false +The Flask app debug mode setting. + +**!!! NOTICE !!!** - Enabling debug mode will cause the debugger to run during app initialization. + +#### * FLASK_APP = /srv/app/run.py +The Flask Python file that should be treated as the main app file. + +#### * FLASK_CONF +The Flask Python config file path that should be loaded during app initialization. + +#### * PDNS_ADMIN_LOG_LEVEL = WARNING +The log output level that the app logging mechanism will use. + +#### * LOG_LEVEL = ? +TODO: Find out where this is used and what it's default value is + +#### * BIND_ADDRESS = 127.0.0.1 +Refer to the PDA_BIND_ADDRESS setting documentation. + +#### * PORT = 9191 +Refer to the PDA_BIND_PORT setting documentation. + +#### * DEBUG_MODE = false +Refer to the PDA_DEBUG setting documentation. + +#### * SALT = ? +TODO: Find out where this is used and what it's default value is + +TODO: Reassign to PDA_ scoped key + +#### * SECRET_KEY = ? +This should be set to a long, random string. [[see here for additional information]](https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY) + +TODO: Reassign to PDA_ scoped key + +#### * SIGNUP_ENABLED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * HSTS_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OFFLINE_MODE = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * FILE_SYSTEM_SESSIONS_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * LOCAL_DB_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * LDAP_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_USER = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_PASSWORD = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_HOST = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_PORT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLALCHEMY_DATABASE_URI = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLALCHEMY_TRACK_MODIFICATIONS = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_SERVER = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_PORT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_DEBUG = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_USE_TLS = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_USE_SSL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_USERNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_PASSWORD = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_DEFAULT_SENDER = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * REMOTE_USER_LOGOUT_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * REMOTE_USER_COOKIES = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OIDC_OAUTH_API_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OIDC_OAUTH_TOKEN_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OIDC_OAUTH_AUTHORIZE_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ENABLED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_DEBUG = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_PATH = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ASSERTION_ENCRYPTED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_METADATA_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_METADATA_CACHE_LIFETIME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_IDP_SSO_BINDING = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_IDP_ENTITY_ID = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_NAMEID_FORMAT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_EMAIL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_GIVENNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_SURNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_USERNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_ADMIN = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_GROUP = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_CERT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_KEY = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_GROUP_ADMIN_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_GROUP_TO_ACCOUNT_MAPPING = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_ACCOUNT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SP_ENTITY_ID = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SP_CONTACT_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SP_CONTACT_MAIL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SIGN_REQUEST = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_WANT_MESSAGE_SIGNED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_LOGOUT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_LOGOUT_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key diff --git a/package.json b/package.json index 76982c8..69a984b 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "bootstrap-datepicker": "^1.8.0", "bootstrap-validator": "^0.11.9", "datatables.net-plugins": "^1.10.19", + "font-awesome": "^4.7.0", "icheck": "^1.0.2", "jquery-slimscroll": "^1.3.8", "jquery-ui-dist": "^1.12.1", diff --git a/powerdnsadmin/__init__.py b/powerdnsadmin/__init__.py index c70b273..1de657b 100755 --- a/powerdnsadmin/__init__.py +++ b/powerdnsadmin/__init__.py @@ -9,11 +9,17 @@ from flask_session import Session from .lib import utils -def create_app(config=None): +def create_app(config=None, other=''): + import urllib from . import models, routes, services from .assets import assets + from .lib import config_util app = Flask(__name__) + ######################################## + # LOGGING SETUP + ######################################## + # Read log level from environment variable log_level_name = os.environ.get('PDNS_ADMIN_LOG_LEVEL', 'WARNING') log_level = logging.getLevelName(log_level_name.upper()) @@ -23,17 +29,18 @@ def create_app(config=None): format= "[%(asctime)s] [%(filename)s:%(lineno)d] %(levelname)s - %(message)s") - # If we use Docker + Gunicorn, adjust the - # log handler - if "GUNICORN_LOGLEVEL" in os.environ: + # Reconfigure the log handler if gunicorn is being ran in Docker + if "PDA_GUNICORN_ENABLED" in os.environ and bool(os.environ['PDA_GUNICORN_ENABLED']): gunicorn_logger = logging.getLogger("gunicorn.error") app.logger.handlers = gunicorn_logger.handlers app.logger.setLevel(gunicorn_logger.level) - # Proxy + # Proxy Support app.wsgi_app = ProxyFix(app.wsgi_app) - # CSRF protection + ######################################## + # CSRF Protection Setup + ######################################## csrf = SeaSurf(app) csrf.exempt(routes.index.dyndns_checkip) csrf.exempt(routes.index.dyndns_update) @@ -58,38 +65,71 @@ def create_app(config=None): csrf.exempt(routes.api.api_zone_cryptokeys) csrf.exempt(routes.api.api_zone_cryptokey) - # Load config from env variables if using docker - if os.path.exists(os.path.join(app.root_path, 'docker_config.py')): - app.config.from_object('powerdnsadmin.docker_config') - else: - # Load default configuration - app.config.from_object('powerdnsadmin.default_config') + ######################################## + # CONFIGURATION SETUP + ######################################## - # Load config file from FLASK_CONF env variable + # Load default configuration + app.config.from_object('configs.default') + + # Load config file from path given in FLASK_CONF env variable if 'FLASK_CONF' in os.environ: app.config.from_envvar('FLASK_CONF') - # Load app sepecified configuration + # Load instance specific configuration if config is not None: if isinstance(config, dict): app.config.update(config) elif config.endswith('.py'): app.config.from_pyfile(config) + # Load configuration from environment variables + config_util.load_config_from_env(app) + + # If no SQLA DB URI is set but SQLA MySQL settings are present, then create DB URI setting + if 'SQLALCHEMY_DATABASE_URI' not in app.config and 'SQLA_DB_USER' in app.config \ + and 'SQLA_DB_PASSWORD' in app.config and 'SQLA_DB_HOST' in app.config and 'SQLA_DB_PORT' in app.config \ + and 'SQLA_DB_NAME' in app.config: + app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://{}:{}@{}:{}/{}'.format( + urllib.parse.quote_plus(app.config.get('SQLA_DB_USER')), + urllib.parse.quote_plus(app.config.get('SQLA_DB_PASSWORD')), + app.config.get('SQLA_DB_HOST'), + app.config.get('SQLA_DB_PORT'), + app.config.get('SQLA_DB_NAME') + ) + else: + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////srv/app/pdns.db' + + ######################################## + # SSL SETUP + ######################################## + # HSTS if app.config.get('HSTS_ENABLED'): from flask_sslify import SSLify _sslify = SSLify(app) # lgtm [py/unused-local-variable] + ######################################## + # SESSION SETUP + ######################################## + # Load Flask-Session if app.config.get('FILESYSTEM_SESSIONS_ENABLED'): app.config['SESSION_TYPE'] = 'filesystem' sess = Session() sess.init_app(app) + ######################################## + # MAIL SETUP + ######################################## + # SMTP app.mail = Mail(app) + ######################################## + # FLASK APP SETUP + ######################################## + # Load app's components assets.init_app(app) models.init_app(app) diff --git a/powerdnsadmin/default_config.py b/powerdnsadmin/default_config.py deleted file mode 100644 index 16b8161..0000000 --- a/powerdnsadmin/default_config.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -import urllib.parse -basedir = os.path.abspath(os.path.dirname(__file__)) - -### BASIC APP CONFIG -SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu' -SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2' -BIND_ADDRESS = '0.0.0.0' -PORT = 9191 -HSTS_ENABLED = False -OFFLINE_MODE = False -FILESYSTEM_SESSIONS_ENABLED = False - -### DATABASE CONFIG -SQLA_DB_USER = 'pda' -SQLA_DB_PASSWORD = 'changeme' -SQLA_DB_HOST = '127.0.0.1' -SQLA_DB_NAME = 'pda' -SQLALCHEMY_TRACK_MODIFICATIONS = True - -### DATABASE - MySQL -SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( - urllib.parse.quote_plus(SQLA_DB_USER), - urllib.parse.quote_plus(SQLA_DB_PASSWORD), - SQLA_DB_HOST, - SQLA_DB_NAME -) - -### DATABASE - SQLite -# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') - -# SAML Authnetication -SAML_ENABLED = False -SAML_ASSERTION_ENCRYPTED = True diff --git a/powerdnsadmin/env/boolean.env b/powerdnsadmin/env/boolean.env new file mode 100644 index 0000000..73a3bf6 --- /dev/null +++ b/powerdnsadmin/env/boolean.env @@ -0,0 +1,26 @@ +PDA_DEBUG +PDA_GUNICORN_ENABLED +PDA_LOAD_ENV_FILES +PDA_AUTOINIT_ENABLED +PDA_AUTOINIT_FORCE +PDA_CHECK_MYSQL_ENABLED +PDA_CHECK_API_ENABLED +PDA_CHECK_PYTEST_ENABLED +DEBUG_MODE +SIGNUP_ENABLED +HSTS_ENABLED +OFFLINE_MODE +FILESYSTEM_SESSIONS_ENABLED +SQLALCHEMY_TRACK_MODIFICATIONS +LOCAL_DB_ENABLED +REMOTE_USER_ENABLED +LDAP_ENABLED +MAIL_DEBUG +MAIL_USE_TLS +MAIL_USE_SSL +SAML_ENABLED +SAML_DEBUG +SAML_SIGN_REQUEST +SAML_WANT_MESSAGE_SIGNED +SAML_LOGOUT +SAML_ASSERTION_ENCRYPTED \ No newline at end of file diff --git a/powerdnsadmin/env/default.env b/powerdnsadmin/env/default.env new file mode 100644 index 0000000..2e3a7a9 --- /dev/null +++ b/powerdnsadmin/env/default.env @@ -0,0 +1,31 @@ +PDA_UID +PDA_USER +PDA_GID +PDA_GROUP +PDA_BIND_ADDRESS +PDA_BIND_PORT +PDA_DEBUG +PDA_LOAD_ENV_FILES +PDA_AUTOINIT_ENABLED +PDA_AUTOINIT_FORCE +PDA_CHECK_MYSQL_ENABLED +PDA_CHECK_MYSQL_FAIL_DELAY +PDA_CHECK_MYSQL_SUCCESS_DELAY +PDA_CHECK_MYSQL_ATTEMPTS +PDA_CHECK_API_ENABLED +PDA_CHECK_API_FAIL_DELAY +PDA_CHECK_API_SUCCESS_DELAY +PDA_CHECK_API_ATTEMPTS +PDA_CHECK_PYTEST_ENABLED +PDA_GUNICORN_ENABLED +PDA_GUNICORN_TIMEOUT +PDA_GUNICORN_WORKERS +PDA_GUNICORN_LOGLEVEL +PDA_GUNICORN_ARGS +PDA_PDNS_API_KEY +PDA_PDNS_API_VERSION +PDA_PDNS_API_PROTO +PDA_PDNS_API_HOST +PDA_PDNS_API_PORT +PDA_ADMIN_USER_USERNAME +PDA_ADMIN_USER_PASSWORD \ No newline at end of file diff --git a/powerdnsadmin/env/discovered.env b/powerdnsadmin/env/discovered.env new file mode 100644 index 0000000..97ff0f6 --- /dev/null +++ b/powerdnsadmin/env/discovered.env @@ -0,0 +1,75 @@ +LC_ALL +LANG +LANGUAGE + +SERVER_NAME +BIND_ADDRESS +PORT +DEBUG_MODE +SALT +SECRET_KEY +SESSION_TYPE +PDNS_ADMIN_LOG_LEVEL + +OFFLINE_MODE +FILESYSTEM_SESSIONS_ENABLED +HSTS_ENABLED + +GUNICORN_LOGLEVEL +GUNICORN_WORKERS +GUNICORN_TIMEOUT +GUNICORN_ARGS + +FLASK_APP +FLASK_ENV +FLASK_CONF + +SQLA_DB_HOST +SQLA_DB_PORT +SQLA_DB_USER +SQLA_DB_PASSWORD +SQLA_DB_NAME +SQLALCHEMY_TRACK_MODIFICATIONS +SQLALCHEMY_DATABASE_URI + +MAIL_SERVER +MAIL_PORT +MAIL_DEBUG +MAIL_USE_TLS +MAIL_USE_SSL +MAIL_USERNAME +MAIL_PASSWORD +MAIL_DEFAULT_SENDER + +SAML_ENABLED +SAML_DEBUG +SAML_PATH +SAML_METADATA_URL +SAML_METADATA_CACHE_LIFETIME +SAML_IDP_SSO_BINDING +SAML_IDP_ENTITY_ID +SAML_NAMEID_FORMAT +SAML_SP_REQUESTED_ATTRIBUTES +SAML_ASSERTION_ENCRYPTED + +SAML_ATTRIBUTE_EMAIL +SAML_ATTRIBUTE_GIVENNAME +SAML_ATTRIBUTE_SURNAME +SAML_ATTRIBUTE_USERNAME +SAML_ATTRIBUTE_ADMIN +SAML_ATTRIBUTE_ACCOUNT + +SAML_SP_ENTITY_ID +SAML_SP_CONTACT_NAME +SAML_SP_CONTACT_MAIL + +SAML_CERT_FILE +SAML_CERT_KEY +SAML_SIGN_REQUEST + +SAML_LOGOUT +SAML_LOGOUT_URL + +REMOTE_USER_ENABLED +REMOTE_USER_LOGOUT_URL +REMOTE_USER_COOKIES diff --git a/powerdnsadmin/env/integer.env b/powerdnsadmin/env/integer.env new file mode 100644 index 0000000..9665a41 --- /dev/null +++ b/powerdnsadmin/env/integer.env @@ -0,0 +1,8 @@ +PDA_UID +PDA_GID +PDA_BIND_PORT +PDA_PDNS_PORT +PORT +SQLA_DB_PORT +MAIL_PORT +SAML_METADATA_CACHE_LIFETIME \ No newline at end of file diff --git a/powerdnsadmin/env/legacy.env b/powerdnsadmin/env/legacy.env new file mode 100644 index 0000000..181110f --- /dev/null +++ b/powerdnsadmin/env/legacy.env @@ -0,0 +1,61 @@ +PDNS_ADMIN_LOG_LEVEL +LOG_LEVEL +BIND_ADDRESS +PORT +DEBUG_MODE +SALT +SECRET_KEY +SIGNUP_ENABLED +HSTS_ENABLED +OFFLINE_MODE +FILESYSTEM_SESSIONS_ENABLED +LOCAL_DB_ENABLED +LDAP_ENABLED +SQLA_DB_USER +SQLA_DB_PASSWORD +SQLA_DB_HOST +SQLA_DB_PORT +SQLA_DB_NAME +SQLALCHEMY_DATABASE_URI +SQLALCHEMY_TRACK_MODIFICATIONS +MAIL_SERVER +MAIL_PORT +MAIL_DEBUG +MAIL_USE_TLS +MAIL_USE_SSL +MAIL_USERNAME +MAIL_PASSWORD +MAIL_DEFAULT_SENDER +REMOTE_USER_LOGOUT_URL +REMOTE_USER_COOKIES +OIDC_OAUTH_API_URL +OIDC_OAUTH_TOKEN_URL +OIDC_OAUTH_AUTHORIZE_URL +SAML_ENABLED +SAML_DEBUG +SAML_PATH +SAML_ASSERTION_ENCRYPTED +SAML_METADATA_URL +SAML_METADATA_CACHE_LIFETIME +SAML_IDP_SSO_BINDING +SAML_IDP_ENTITY_ID +SAML_NAMEID_FORMAT +SAML_ATTRIBUTE_EMAIL +SAML_ATTRIBUTE_GIVENNAME +SAML_ATTRIBUTE_SURNAME +SAML_ATTRIBUTE_NAME +SAML_ATTRIBUTE_USERNAME +SAML_ATTRIBUTE_ADMIN +SAML_ATTRIBUTE_GROUP +SAML_CERT +SAML_KEY +SAML_GROUP_ADMIN_NAME +SAML_GROUP_TO_ACCOUNT_MAPPING +SAML_ATTRIBUTE_ACCOUNT +SAML_SP_ENTITY_ID +SAML_SP_CONTACT_NAME +SAML_SP_CONTACT_MAIL +SAML_SIGN_REQUEST +SAML_WANT_MESSAGE_SIGNED +SAML_LOGOUT +SAML_LOGOUT_URL \ No newline at end of file diff --git a/powerdnsadmin/lib/config_util.py b/powerdnsadmin/lib/config_util.py new file mode 100644 index 0000000..942f9b2 --- /dev/null +++ b/powerdnsadmin/lib/config_util.py @@ -0,0 +1,55 @@ +import os + + +def str2bool(v): + return v.lower() in ("true", "yes", "1") + + +def load_env_file(file_name='legacy'): + f = open('/srv/app/powerdnsadmin/env/' + file_name + '.env', 'r') + lines = f.read().splitlines() + f.close() + return lines + + +def load_config_from_env(app): + legacy_vars = load_env_file('legacy') + boolean_vars = load_env_file('boolean') + integer_vars = load_env_file('integer') + config = {} + + for key in os.environ: + config_key = None + val = None + + if (key.startswith('PDA_') or key in legacy_vars) and not key.endswith('_FILE'): + config_key = key + val = os.environ[key] + + if key.startswith('PDAC_') and not key.endswith('_FILE'): + config_key = key[5:] + val = os.environ[key] + + if key.endswith('_FILE'): + config_key = key[:-5] + + if key.startswith('PDAC_'): + config_key = config_key[5:] + + if key.startswith('PDAC_') or config_key in legacy_vars: + with open(os.environ[key]) as f: + val = f.read() + f.close() + + if val is not None: + if config_key in boolean_vars: + val = str2bool(val) + if config_key in integer_vars: + val = int(val) + + if config_key is not None: + config[config_key] = val + + if isinstance(config, dict): + app.config.update(config) + diff --git a/requirements.txt b/requirements.txt index c33d0ce..f8ec9d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ PyYAML==5.4 Flask-SSLify==0.1.5 Flask-Mail==0.9.1 flask-session==0.3.2 +xmlsec==1.3.12 \ No newline at end of file diff --git a/run.py b/run.py index 5512724..1fafc94 100755 --- a/run.py +++ b/run.py @@ -3,4 +3,8 @@ from powerdnsadmin import create_app if __name__ == '__main__': app = create_app() - app.run(debug = True, host=app.config.get('BIND_ADDRESS', '127.0.0.1'), port=app.config.get('PORT', '9191')) + app.run( + host=app.config.get('BIND_ADDRESS', app.config.get('PDA_BIND_ADDRESS', '0.0.0.0')), + port=app.config.get('PORT', app.config.get('PDA_BIND_PORT', 80)), + debug=app.config.get('DEBUG_MODE', app.config.get('PDA_DEBUG', False)) + ) diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index e3b95c5..0000000 --- a/yarn.lock +++ /dev/null @@ -1,1151 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -JSONStream@^1.0.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -acorn-node@^1.2.0, acorn-node@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.3.0.tgz#5f86d73346743810ef1269b901dbcbded020861b" - dependencies: - acorn "^5.4.1" - xtend "^4.0.1" - -acorn@^4.0.3: - version "4.0.13" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" - -acorn@^5.4.1: - version "5.6.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.6.2.tgz#b1da1d7be2ac1b4a327fb9eab851702c5045b4e7" - -admin-lte@2.4.9: - version "2.4.9" - resolved "https://registry.yarnpkg.com/admin-lte/-/admin-lte-2.4.9.tgz#effc04dbb587ccb7d674d4cf663cc141f925ed73" - integrity sha512-+u3zt5sWPPT8HmBvRg4F8IGZPaE5wD0K10+IjXoynfe/jEUrMMj+4eA1LGH9fMK6QulmFr1NCtc1Tk+mTgC24A== - dependencies: - bootstrap "^3.4.0" - bootstrap-colorpicker "^2.5.3" - bootstrap-datepicker "^1.8.0" - bootstrap-daterangepicker "^2.1.25" - bootstrap-slider "^9.8.0" - bootstrap-timepicker "^0.5.2" - chart.js "1.0.*" - ckeditor "^4.11.2" - datatables.net "^1.10.19" - datatables.net-bs "^1.10.19" - fastclick "^1.0.6" - flot "^0.8.3" - font-awesome "^4.7.0" - fullcalendar "^3.10.0" - inputmask "^3.3.7" - ion-rangeslider "^2.3.0" - ionicons "^3.0.0" - jquery "^3.2.1" - jquery-knob "^1.2.11" - jquery-sparkline "^2.4.0" - jquery-ui "^1.12.1" - jvectormap "^1.2.2" - moment "^2.24.0" - morris.js "^0.5.0" - pace "0.0.4" - raphael "^2.2.7" - select2 "^4.0.3" - slimscroll "^0.9.1" - -almond@~0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/almond/-/almond-0.3.3.tgz#a0e7c95ac7624d6417b4494b1e68bff693168a20" - -array-filter@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - -array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - -array-reduce@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -assert@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - dependencies: - util "0.10.3" - -astw@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/astw/-/astw-2.2.0.tgz#7bd41784d32493987aeb239b6b4e1c57a873b917" - dependencies: - acorn "^4.0.3" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bootstrap-colorpicker@^2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/bootstrap-colorpicker/-/bootstrap-colorpicker-2.5.3.tgz#b50aff8590fbaa6b5aa63a5624e4213f1659a49d" - integrity sha512-xdllX8LSMvKULs3b8JrgRXTvyvjkSMHHHVuHjjN5FNMqr6kRe5NPiMHFmeAFjlgDF73MspikudLuEwR28LbzLw== - dependencies: - jquery ">=1.10" - -bootstrap-datepicker@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/bootstrap-datepicker/-/bootstrap-datepicker-1.8.0.tgz#c63513931e6f09f16ae9f11b62f32d950df3958e" - integrity sha512-213St/G8KT3mjs4qu4qwww74KWysMaIeqgq5OhrboZjIjemIpyuxlSo9FNNI5+KzpkkxkRRba+oewiRGV42B1A== - dependencies: - jquery ">=1.7.1 <4.0.0" - -bootstrap-daterangepicker@^2.1.25: - version "2.1.30" - resolved "https://registry.yarnpkg.com/bootstrap-daterangepicker/-/bootstrap-daterangepicker-2.1.30.tgz#f893dbfff5a4d7dfaab75460e8ea6969bb89689a" - dependencies: - jquery ">=1.10" - moment "^2.9.0" - -bootstrap-slider@^9.8.0: - version "9.10.0" - resolved "https://registry.yarnpkg.com/bootstrap-slider/-/bootstrap-slider-9.10.0.tgz#1103d6bc00cfbfa8cfc9a2599ab518c55643da3f" - -bootstrap-timepicker@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/bootstrap-timepicker/-/bootstrap-timepicker-0.5.2.tgz#10ed9f2a2f0b8ccaefcde0fcf6a0738b919a3835" - -bootstrap-validator@^0.11.9: - version "0.11.9" - resolved "https://registry.yarnpkg.com/bootstrap-validator/-/bootstrap-validator-0.11.9.tgz#fb7058eef53623e78f5aa7967026f98f875a9404" - -bootstrap@^3.4.0, bootstrap@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72" - integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browser-pack@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.8.0" - defined "^1.0.0" - safe-buffer "^5.1.1" - through2 "^2.0.0" - umd "^3.0.0" - -browser-resolve@^1.11.0, browser-resolve@^1.7.0: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" - dependencies: - resolve "1.1.7" - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c" - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - dependencies: - pako "~1.0.5" - -browserify@>=3.46.0: - version "16.2.2" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.2.tgz#4b1f66ba0e54fa39dbc5aa4be9629142143d91b0" - dependencies: - JSONStream "^1.0.3" - assert "^1.4.0" - browser-pack "^6.0.1" - browser-resolve "^1.11.0" - browserify-zlib "~0.2.0" - buffer "^5.0.2" - cached-path-relative "^1.0.0" - concat-stream "^1.6.0" - console-browserify "^1.1.0" - constants-browserify "~1.0.0" - crypto-browserify "^3.0.0" - defined "^1.0.0" - deps-sort "^2.0.0" - domain-browser "^1.2.0" - duplexer2 "~0.1.2" - events "^2.0.0" - glob "^7.1.0" - has "^1.0.0" - htmlescape "^1.1.0" - https-browserify "^1.0.0" - inherits "~2.0.1" - insert-module-globals "^7.0.0" - labeled-stream-splicer "^2.0.0" - mkdirp "^0.5.0" - module-deps "^6.0.0" - os-browserify "~0.3.0" - parents "^1.0.1" - path-browserify "~0.0.0" - process "~0.11.0" - punycode "^1.3.2" - querystring-es3 "~0.2.0" - read-only-stream "^2.0.0" - readable-stream "^2.0.2" - resolve "^1.1.4" - shasum "^1.0.0" - shell-quote "^1.6.1" - stream-browserify "^2.0.0" - stream-http "^2.0.0" - string_decoder "^1.1.1" - subarg "^1.0.0" - syntax-error "^1.1.1" - through2 "^2.0.0" - timers-browserify "^1.0.1" - tty-browserify "0.0.1" - url "~0.11.0" - util "~0.10.1" - vm-browserify "^1.0.0" - xtend "^4.0.0" - -buffer-from@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - -buffer@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.1.0.tgz#c913e43678c7cb7c8bd16afbcddb6c5505e8f9fe" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - -cached-path-relative@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" - integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== - -charm@~0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/charm/-/charm-0.1.2.tgz#06c21eed1a1b06aeb67553cdc53e23274bac2296" - -chart.js@1.0.*: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-1.0.2.tgz#ad57d2229cfd8ccf5955147e8121b4911e69dfe7" - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -ckeditor@^4.11.2: - version "4.11.3" - resolved "https://registry.yarnpkg.com/ckeditor/-/ckeditor-4.11.3.tgz#91f66d7ddb5bff3874514fe539779686874ed655" - integrity sha512-v6G+v16WAcukKuQH6m+trA9RCOQntFM2nxPO/GFiLYsdD/5IbZAZ2n7GwMxQmxDXpx4AixpnMWF+yAE1t9wq6Q== - -classie@>=0.0.1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/classie/-/classie-1.0.0.tgz#fc9b29b47e64e374a2062fb624d05a61cd703ab2" - -combine-source-map@^0.8.0, combine-source-map@~0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - -concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - -constants-browserify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - -convert-source-map@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -crypto-browserify@^3.0.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -datatables.net-bs@^1.10.19: - version "1.10.19" - resolved "https://registry.yarnpkg.com/datatables.net-bs/-/datatables.net-bs-1.10.19.tgz#08763b4e4d0cef1a427d019dc15e717c7ed67a4d" - integrity sha512-5gxoI2n+duZP06+4xVC2TtH6zcY369/TRKTZ1DdSgDcDUl4OYQsrXCuaLJmbVzna/5Y5lrMmK7CxgvYgIynICA== - dependencies: - datatables.net "1.10.19" - jquery ">=1.7" - -datatables.net-plugins@^1.10.19: - version "1.10.20" - resolved "https://registry.yarnpkg.com/datatables.net-plugins/-/datatables.net-plugins-1.10.20.tgz#c89f6bed3fa7e6605cbeaa35d60f223659e84c8c" - integrity sha512-rnhNmRHe9UEzvM7gtjBay1QodkWUmxLUhHNbmJMYhhUggjtm+BRSlE0PRilkeUkwckpNWzq+0fPd7/i0fpQgzA== - -datatables.net@1.10.19, datatables.net@^1.10.19: - version "1.10.19" - resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.10.19.tgz#97a1ed41c85e62d61040603481b59790a172dd1f" - integrity sha512-+ljXcI6Pj3PTGy5pesp3E5Dr3x3AV45EZe0o1r0gKENN2gafBKXodVnk2ypKwl2tTmivjxbkiqoWnipTefyBTA== - dependencies: - jquery ">=1.7" - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - -deps-sort@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5" - dependencies: - JSONStream "^1.0.3" - shasum "^1.0.0" - subarg "^1.0.0" - through2 "^2.0.0" - -des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -detective@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.1.0.tgz#7a20d89236d7b331ccea65832e7123b5551bb7cb" - dependencies: - acorn-node "^1.3.0" - defined "^1.0.0" - minimist "^1.1.1" - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -domain-browser@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - -domhelper@~0.9.0: - version "0.9.1" - resolved "https://registry.yarnpkg.com/domhelper/-/domhelper-0.9.1.tgz#26554e5bac2c9e9585dca500978df5067d64bd00" - dependencies: - browserify ">=3.46.0" - classie ">=0.0.1" - util-extend "^1.0.1" - -duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" - -elliptic@^6.0.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -eve-raphael@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/eve-raphael/-/eve-raphael-0.5.0.tgz#17c754b792beef3fa6684d79cf5a47c63c4cda30" - -events@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -fastclick@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/fastclick/-/fastclick-1.0.6.tgz#161625b27b1a5806405936bda9a2c1926d06be6a" - -flot@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/flot/-/flot-0.8.3.tgz#6d9b93c7aa2cfb30dfa1af9c1ec4c94070b1217f" - integrity sha512-xg2otcTJDvS+ERK+my4wxG/ASq90QURXtoM4LhacCq0jQW2jbyjdttbRNqU2cPykrpMvJ6b2uSp6SAgYAzj9tQ== - -font-awesome@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - -fullcalendar@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/fullcalendar/-/fullcalendar-3.10.0.tgz#cc5e87d518fd6550e142816a31dd191664847919" - integrity sha512-0OtsHhmdYhtFmQwXzyo8VqHzYgamg+zVOoytv5N13gI+iF6CGjevpCi/yBaQs0O4wY3OAp8I688IxdNYe0iAvw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - -glob@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -has@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -htmlescape@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - -icheck@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/icheck/-/icheck-1.0.2.tgz#06d08da3d47ae448c153b2639b86e9ad7fdf7128" - -ieee754@^1.1.4: - version "1.1.12" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -inline-source-map@~0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" - dependencies: - source-map "~0.5.3" - -inputmask@^3.3.7: - version "3.3.11" - resolved "https://registry.yarnpkg.com/inputmask/-/inputmask-3.3.11.tgz#1421c94ae28c3dcd1b4d26337b508bb34998e2d8" - -insert-module-globals@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.1.0.tgz#dbb3cea71d3a43d5a07ef0310fe5f078aa4dbf35" - dependencies: - JSONStream "^1.0.3" - combine-source-map "^0.8.0" - concat-stream "^1.6.1" - is-buffer "^1.1.0" - lexical-scope "^1.2.0" - path-is-absolute "^1.0.1" - process "~0.11.0" - through2 "^2.0.0" - xtend "^4.0.0" - -ion-rangeslider@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/ion-rangeslider/-/ion-rangeslider-2.3.0.tgz#7957ce2e78acfc956b8c43009373da91f743347e" - integrity sha512-7TtH9/X4Aq/xCzboWxjwlv20gVqR90Ysc3aehMlTuH2/ULaSxpB80hq+yvD1N0FwWbPDtxQpjQrz/iX+LWXKmg== - dependencies: - jquery ">=1.8" - -ionicons@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ionicons/-/ionicons-3.0.0.tgz#40b8daf4fd7a31150bd002160f66496e22a98c3c" - -is-buffer@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - -isarray@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.4.tgz#38e7bcbb0f3ba1b7933c86ba1894ddfc3781bbb7" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -jquery-knob@^1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/jquery-knob/-/jquery-knob-1.2.11.tgz#f37c39dbc1c7a6a6c12cdb2ed4f6bffb683f10d6" - -jquery-mousewheel@~3.1.13: - version "3.1.13" - resolved "https://registry.yarnpkg.com/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz#06f0335f16e353a695e7206bf50503cb523a6ee5" - -jquery-slimscroll@^1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/jquery-slimscroll/-/jquery-slimscroll-1.3.8.tgz#8481c44e7a47687653908a28f7f70aed64c84e36" - dependencies: - jquery ">= 1.7" - -jquery-sparkline@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jquery-sparkline/-/jquery-sparkline-2.4.0.tgz#1be8b7b704dd3857152708aefb1d4a4b3a69fb33" - -jquery-ui-dist@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz#5c0815d3cc6f90ff5faaf5b268a6e23b4ca904fa" - -jquery-ui@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" - -jquery.quicksearch@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jquery.quicksearch/-/jquery.quicksearch-2.4.0.tgz#240c9f435e936c63bf8fbba355144ffdddf9ea26" - integrity sha512-20FJSCW3kTawO6Jvy/6MtUCURvgUZFqRUOAGTxH/VaPlwLG4kba82sKaM3ghTi1DsmSZrM2BvrwLUwNWmwDXiw== - dependencies: - jquery ">=1.8" - -"jquery@>= 1.7", "jquery@>= 1.7.1", jquery@>=1.10, jquery@>=1.5, jquery@>=1.7, "jquery@>=1.7.1 <4.0.0", jquery@>=1.8, jquery@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" - -json-stable-stringify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" - dependencies: - jsonify "~0.0.0" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - -jtimeout@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jtimeout/-/jtimeout-3.1.0.tgz#4cd65b28eff8b9f8c61d08889a9ac3abdf5d9893" - integrity sha512-xA2TlImMGj4c0yAiM9BUq+8aAFVYVYUX2tkcC8u8das9qoZSs13SxhVcfWqI4cHOsv3huj2D0VRNHeVCLO3mVQ== - dependencies: - jquery ">=1.7.1 <4.0.0" - -jvectormap@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jvectormap/-/jvectormap-1.2.2.tgz#2e4408b24a60473ff106c1e7243e375ae5ca85da" - dependencies: - jquery ">=1.5" - -labeled-stream-splicer@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz#9cffa32fd99e1612fd1d86a8db962416d5292926" - dependencies: - inherits "^2.0.1" - isarray "^2.0.4" - stream-splicer "^2.0.0" - -lexical-scope@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/lexical-scope/-/lexical-scope-1.2.0.tgz#fcea5edc704a4b3a8796cdca419c3a0afaf22df4" - dependencies: - astw "^2.0.0" - -lodash.memoize@~3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" - -md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@^1.1.0, minimist@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -mkdirp@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - -module-deps@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.1.0.tgz#d1e1efc481c6886269f7112c52c3236188e16479" - dependencies: - JSONStream "^1.0.3" - browser-resolve "^1.7.0" - cached-path-relative "^1.0.0" - concat-stream "~1.6.0" - defined "^1.0.0" - detective "^5.0.2" - duplexer2 "^0.1.2" - inherits "^2.0.1" - parents "^1.0.0" - readable-stream "^2.0.2" - resolve "^1.4.0" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - -moment@^2.24.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== - -moment@^2.9.0: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - -morris.js@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/morris.js/-/morris.js-0.5.0.tgz#725767135cfae059aae75999bb2ce6a1c5d1b44b" - -multiselect@^0.9.12: - version "0.9.12" - resolved "https://registry.yarnpkg.com/multiselect/-/multiselect-0.9.12.tgz#d15536e986dd6a0029b160d6613bcedf81e4c7ed" - dependencies: - jquery ">= 1.7.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - -os-browserify@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - -pace@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/pace/-/pace-0.0.4.tgz#d66405d5f5bc12d25441a6e26c878dbc69e77a77" - dependencies: - charm "~0.1.0" - -pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - -parents@^1.0.0, parents@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" - dependencies: - path-platform "~0.11.15" - -parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - -path-browserify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" - -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - -path-platform@~0.11.15: - version "0.11.15" - resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" - -pbkdf2@^3.0.3: - version "3.0.16" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - -process@~0.11.0: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - -public-encrypt@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - -querystring-es3@~0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -raphael@^2.2.7: - version "2.2.7" - resolved "https://registry.yarnpkg.com/raphael/-/raphael-2.2.7.tgz#231b19141f8d086986d8faceb66f8b562ee2c810" - dependencies: - eve-raphael "0.5.0" - -read-only-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" - dependencies: - readable-stream "^2.0.2" - -readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -resolve@1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - -resolve@^1.1.4, resolve@^1.4.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - dependencies: - path-parse "^1.0.5" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - -select2@^4.0.3: - version "4.0.5" - resolved "https://registry.yarnpkg.com/select2/-/select2-4.0.5.tgz#7aac50692561985b34d3b82ec55e226f8960d40a" - dependencies: - almond "~0.3.1" - jquery-mousewheel "~3.1.13" - -sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shasum@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" - dependencies: - json-stable-stringify "~0.0.0" - sha.js "~2.4.4" - -shell-quote@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - dependencies: - array-filter "~0.0.0" - array-map "~0.0.0" - array-reduce "~0.0.0" - jsonify "~0.0.0" - -slimscroll@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/slimscroll/-/slimscroll-0.9.1.tgz#f675cdc601d80ada20f16004d227d156fd1187b2" - dependencies: - browserify ">=3.46.0" - classie ">=0.0.1" - domhelper "~0.9.0" - util-extend "^1.0.1" - -source-map@~0.5.3: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -stream-browserify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - -stream-http@^2.0.0: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-splicer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.2" - -string_decoder@^1.1.1, string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - dependencies: - safe-buffer "~5.1.0" - -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - dependencies: - minimist "^1.1.0" - -syntax-error@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" - dependencies: - acorn-node "^1.2.0" - -through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -"through@>=2.2.7 <3": - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - -timers-browserify@^1.0.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" - dependencies: - process "~0.11.0" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - -tty-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - -umd@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" - -url@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -util-extend@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - dependencies: - inherits "2.0.1" - -util@~0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - dependencies: - inherits "2.0.3" - -vm-browserify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.0.1.tgz#a15d7762c4c48fa6bf9f3309a21340f00ed23063" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"