Update docker stuff and bug fixes

This commit is contained in:
Khanh Ngo
2019-12-04 11:50:46 +07:00
parent 8ea00b9484
commit 840e2a4750
37 changed files with 444 additions and 1082 deletions

View File

@ -2,58 +2,9 @@ import os
from werkzeug.contrib.fixers import ProxyFix
from flask import Flask
from flask_seasurf import SeaSurf
from flask_sslify import SSLify
from .lib import utils
# from flask_login import LoginManager
# from flask_sqlalchemy import SQLAlchemy as SA
# from flask_migrate import Migrate
# from authlib.flask.client import OAuth as AuthlibOAuth
# from sqlalchemy.exc import OperationalError
# from app.assets import assets
# ### SYBPATCH ###
# from app.customboxes import customBoxes
### SYBPATCH ###
# subclass SQLAlchemy to enable pool_pre_ping
# class SQLAlchemy(SA):
# def apply_pool_defaults(self, app, options):
# SA.apply_pool_defaults(self, app, options)
# options["pool_pre_ping"] = True
# app = Flask(__name__)
# app.config.from_object('config')
# app.wsgi_app = ProxyFix(app.wsgi_app)
# csrf = SeaSurf(app)
# assets.init_app(app)
# #### CONFIGURE LOGGER ####
# from app.lib.log import logger
# logging = logger('powerdns-admin', app.config['LOG_LEVEL'], app.config['LOG_FILE']).config()
# login_manager = LoginManager()
# login_manager.init_app(app)
# db = SQLAlchemy(app) # database
# migrate = Migrate(app, db) # flask-migrate
# authlib_oauth_client = AuthlibOAuth(app) # authlib oauth
# if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'):
# from app.lib import certutil
# if not certutil.check_certificate():
# certutil.create_self_signed_cert()
# from app import models
# from app.blueprints.api import api_blueprint
# app.register_blueprint(api_blueprint, url_prefix='/api/v1')
# from app import views
def create_app(config=None):
from . import models, routes, services
@ -63,9 +14,6 @@ def create_app(config=None):
# Proxy
app.wsgi_app = ProxyFix(app.wsgi_app)
# HSTS enabled
_sslify = SSLify(app)
# CSRF protection
csrf = SeaSurf(app)
csrf.exempt(routes.index.dyndns_checkip)
@ -80,10 +28,14 @@ def create_app(config=None):
csrf.exempt(routes.api.api_zone_forward)
csrf.exempt(routes.api.api_create_zone)
# Load default configuration
app.config.from_object('powerdnsadmin.default_config')
# 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')
# Load environment configuration
# Load config file from FLASK_CONF env variable
if 'FLASK_CONF' in os.environ:
app.config.from_envvar('FLASK_CONF')
@ -94,6 +46,11 @@ def create_app(config=None):
elif config.endswith('.py'):
app.config.from_pyfile(config)
# HSTS
if app.config.get('HSTS_ENABLED'):
from flask_sslify import SSLify
_sslify = SSLify(app)
# Load app's components
assets.init_app(app)
models.init_app(app)

View File

@ -1,102 +1,25 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))
basedir = os.path.abspath(os.path.abspath(os.path.dirname(__file__)))
### BASIC APP CONFIG
SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu'
# BASIC APP CONFIG
SECRET_KEY = 'We are the world'
SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2'
BIND_ADDRESS = '0.0.0.0'
PORT = 9191
HSTS_ENABLED = False
# TIMEOUT - for large zones
TIMEOUT = 10
# LOG CONFIG
# - For docker, LOG_FILE=''
LOG_LEVEL = 'DEBUG'
# DATABASE CONFIG
### 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
# DATBASE - MySQL
### DATBASE - MySQL
SQLALCHEMY_DATABASE_URI = 'mysql://'+SQLA_DB_USER+':'+SQLA_DB_PASSWORD+'@'+SQLA_DB_HOST+'/'+SQLA_DB_NAME
# DATABSE - SQLite
#SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
### DATABSE - SQLite
# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')
# SAML Authnetication
SAML_ENABLED = False
# SAML_DEBUG = True
# SAML_PATH = os.path.join(os.path.dirname(__file__), 'saml')
# ##Example for ADFS Metadata-URL
# SAML_METADATA_URL = 'https://<hostname>/FederationMetadata/2007-06/FederationMetadata.xml'
# #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_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
# #SAML_NAMEID_FORMAT = 'urn:oid:0.9.2342.19200300.100.1.1'
# ## Attribute to use for Email address
# ### Default: email
# ### Example: urn:oid:0.9.2342.19200300.100.1.3
# #SAML_ATTRIBUTE_EMAIL = 'urn:oid:0.9.2342.19200300.100.1.3'
# ## Attribute to use for Given name
# ### Default: givenname
# ### Example: urn:oid:2.5.4.42
# #SAML_ATTRIBUTE_GIVENNAME = 'urn:oid:2.5.4.42'
# ## Attribute to use for Surname
# ### Default: surname
# ### Example: urn:oid:2.5.4.4
# #SAML_ATTRIBUTE_SURNAME = 'urn:oid:2.5.4.4'
# ## Attribute to use for username
# ### Default: Use NameID instead
# ### Example: urn:oid:0.9.2342.19200300.100.1.1
# #SAML_ATTRIBUTE_USERNAME = 'urn:oid:0.9.2342.19200300.100.1.1'
# ## Attribute to get admin status from
# ### Default: Don't control admin with SAML attribute
# ### Example: https://example.edu/pdns-admin
# ### If set, look for the value 'true' to set a user as an administrator
# ### If not included in assertion, or set to something other than 'true',
# ### the user is set as a non-administrator user.
# #SAML_ATTRIBUTE_ADMIN = 'https://example.edu/pdns-admin'
# ## Attribute to get account names from
# ### Default: Don't control accounts with SAML attribute
# ### If set, the user will be added and removed from accounts to match
# ### what's in the login assertion. Accounts that don't exist will
# ### be created and the user added to them.
# SAML_ATTRIBUTE_ACCOUNT = 'https://example.edu/pdns-account'
# SAML_SP_ENTITY_ID = 'http://<SAML SP Entity ID>'
# SAML_SP_CONTACT_NAME = '<contact name>'
# SAML_SP_CONTACT_MAIL = '<contact mail>'
# #Cofigures if SAML tokens should be encrypted.
# #If enabled a new app certificate will be generated on restart
# SAML_SIGN_REQUEST = False
# #Use SAML standard logout mechanism retreived from idp metadata
# #If configured false don't care about SAML session on logout.
# #Logout from PowerDNS-Admin only and keep SAML session authenticated.
# SAML_LOGOUT = False
# #Configure to redirect to a different url then PowerDNS-Admin login after SAML logout
# #for example redirect to google.com after successful saml logout
# #SAML_LOGOUT_URL = 'https://google.com'

View File

@ -12,44 +12,6 @@ from datetime import datetime, timedelta
from threading import Thread
from .certutil import KEY_FILE, CERT_FILE
# import logging as logger
# logging = logger.getLogger(__name__)
# if app.config['SAML_ENABLED']:
# from onelogin.saml2.auth import OneLogin_Saml2_Auth
# from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
# idp_timestamp = datetime(1970, 1, 1)
# idp_data = None
# if 'SAML_IDP_ENTITY_ID' in app.config:
# idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'], entity_id=app.config.get('SAML_IDP_ENTITY_ID', None), required_sso_binding=app.config['SAML_IDP_SSO_BINDING'])
# else:
# idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'], entity_id=app.config.get('SAML_IDP_ENTITY_ID', None))
# if idp_data is None:
# print('SAML: IDP Metadata initial load failed')
# exit(-1)
# idp_timestamp = datetime.now()
# def get_idp_data():
# global idp_data, idp_timestamp
# lifetime = timedelta(minutes=app.config['SAML_METADATA_CACHE_LIFETIME'])
# if idp_timestamp+lifetime < datetime.now():
# background_thread = Thread(target=retrieve_idp_data)
# background_thread.start()
# return idp_data
# def retrieve_idp_data():
# global idp_data, idp_timestamp
# if 'SAML_IDP_SSO_BINDING' in app.config:
# new_idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'], entity_id=app.config.get('SAML_IDP_ENTITY_ID', None), required_sso_binding=app.config['SAML_IDP_SSO_BINDING'])
# else:
# new_idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'], entity_id=app.config.get('SAML_IDP_ENTITY_ID', None))
# if new_idp_data is not None:
# idp_data = new_idp_data
# idp_timestamp = datetime.now()
# print("SAML: IDP Metadata successfully retrieved from: " + app.config['SAML_METADATA_URL'])
# else:
# print("SAML: IDP Metadata could not be retrieved")
def auth_from_url(url):
@ -115,6 +77,8 @@ def fetch_json(remote_url, method='GET', data=None, params=None, headers=None, t
if r.status_code == 204:
return {}
elif r.status_code == 409:
return {'error': 'Resource already exists or conflict', 'http_code': r.status_code}
try:
assert ('json' in r.headers['content-type'])

View File

@ -24,7 +24,7 @@ bravado_config = {
'use_models': True,
}
dir_path = os.path.dirname(os.path.abspath(__file__))
dir_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
spec_path = os.path.join(dir_path, "swagger-spec.yaml")
spec_dict = get_swagger_spec(spec_path)
spec = Spec.from_dict(spec_dict, config=bravado_config)

View File

@ -1,26 +0,0 @@
import sys
import os
import re
import ldap
import ldap.filter
import base64
import bcrypt
import itertools
import traceback
import pyotp
import dns.reversename
import dns.inet
import dns.name
import pytimeparse
import random
import string
from ast import literal_eval
from datetime import datetime
from urllib.parse import urljoin
from distutils.util import strtobool
from distutils.version import StrictVersion
from flask_login import AnonymousUserMixin
from app import db, app
from app.lib import utils
from app.lib.log import logging

View File

@ -243,6 +243,8 @@ class Domain(db.Model):
data=post_data)
if 'error' in jdata.keys():
current_app.logger.error(jdata['error'])
if jdata.get('http_code') == 409:
return {'status': 'error', 'msg': 'Domain already exists'}
return {'status': 'error', 'msg': jdata['error']}
else:
current_app.logger.info(

View File

@ -281,7 +281,7 @@ def api_get_apikeys(domain_name):
if current_user.role.name not in ['Administrator', 'Operator']:
if domain_name:
msg = "Check if domain {0} exists and \
is allowed for user." .format(domain_name)
is allowed for user." .format(domain_name)
current_app.logger.debug(msg)
apikeys = current_user.get_apikeys(domain_name)
@ -502,14 +502,18 @@ def api_create_zone(server_id):
@api_bp.route('/servers/<string:server_id>/zones', methods=['GET'])
@apikey_auth
def api_get_zones(server_id):
if g.apikey.role.name not in ['Administrator', 'Operator']:
domain_obj_list = g.apikey.domains
if server_id == 'powerdns-admin':
if g.apikey.role.name not in ['Administrator', 'Operator']:
domain_obj_list = g.apikey.domains
else:
domain_obj_list = Domain.query.all()
return json.dumps(domain_schema.dump(domain_obj_list)), 200
else:
domain_obj_list = Domain.query.all()
return json.dumps(domain_schema.dump(domain_obj_list)), 200
resp = helper.forward_request()
return resp.content, resp.status_code, resp.headers.items()
#endpoint to snychronize Domains in background
# The endpoint to snychronize Domains in background
@api_bp.route('/sync_domains', methods=['GET'])
@apikey_auth
def sync_domains():

View File

@ -1,4 +1,4 @@
from flask import Blueprint, render_template, make_response, url_for, current_app, request, jsonify
from flask import Blueprint, render_template, make_response, url_for, current_app, request, jsonify, redirect
from flask_login import login_required, current_user
from sqlalchemy import not_, or_
@ -120,7 +120,7 @@ def domains_custom(boxId):
def dashboard():
if not Setting().get('pdns_api_url') or not Setting().get(
'pdns_api_key') or not Setting().get('pdns_version'):
return redirect(url_for('admin_setting_pdns'))
return redirect(url_for('admin.setting_pdns'))
BG_DOMAIN_UPDATE = Setting().get('bg_domain_updates')
if not BG_DOMAIN_UPDATE:

View File

@ -111,7 +111,7 @@ def add():
if ' ' in domain_name or not domain_name or not domain_type:
return render_template('errors/400.html',
msg="Please correct your input"), 400
msg="Please enter a valid domain name"), 400
if domain_type == 'slave':
if request.form.getlist('domain_master_address'):

View File

@ -25,8 +25,12 @@
<i class="fa fa-warning text-yellow"></i> Oops! Bad request
</h3>
<p>
The server refused to process your request and return a 400 error.
You may <a href="{{ url_for('dashboard.dashboard') }}">return to the dashboard</a>.
{% if msg %}
{{ msg }}
{% else %}
The server refused to process your request and return a 400 error.
{% endif %}
<br/>You may <a href="{{ url_for('dashboard.dashboard') }}">return to the dashboard</a>.
</p>
</div>
<!-- /.error-content -->