From 42a989a6b9b6388947da6fc273728a24c19fa686 Mon Sep 17 00:00:00 2001 From: Alex Shatov Date: Tue, 12 Sep 2017 13:00:25 -0400 Subject: 1.0.0 and policy API to deployment-handler * policy API to deployment-handler /policy * removed pycrypto of config - the same way as other apps * simple upload of config to consul - curl * preparation for policy-handler blueprint Change-Id: I424a1ded0795562ea36b5409304cbb8b5a7e8a24 Issue-Id: DCAEGEN2-62 Signed-off-by: Alex Shatov --- etc/config.json | 1 - .../restart_upload_config_for_ph_in_docker.sh | 30 --------- etc_upload/upload_config_for_ph_in_docker.sh | 38 ++---------- policyhandler/config.py | 40 +----------- policyhandler/deploy_handler.py | 22 +++---- policyhandler/onap/crypto.py | 72 ---------------------- policyhandler/policy_engine.py | 22 ++++--- policyhandler/policy_handler.py | 25 +------- requirements.txt | 1 - setup.py | 9 ++- start_ph_in_docker.sh | 14 +++-- 11 files changed, 48 insertions(+), 226 deletions(-) delete mode 100644 etc_upload/restart_upload_config_for_ph_in_docker.sh delete mode 100644 policyhandler/onap/crypto.py diff --git a/etc/config.json b/etc/config.json index 211ce16..e54569b 100644 --- a/etc/config.json +++ b/etc/config.json @@ -1,5 +1,4 @@ { - "config_pwd" : "onap-secret@2017!", "wservice_port" : 25577, "policy_handler" : { "system" : "policy_handler" diff --git a/etc_upload/restart_upload_config_for_ph_in_docker.sh b/etc_upload/restart_upload_config_for_ph_in_docker.sh deleted file mode 100644 index 5fd97a9..0000000 --- a/etc_upload/restart_upload_config_for_ph_in_docker.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. -# ================================================================================ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. - -APPNAME=policy_handler -docker stop ${APPNAME} -docker rm ${APPNAME} -docker rmi ${APPNAME} -docker build -t ${APPNAME} . - -RUNSCRIPT=$(dirname $0)/upload_config_for_ph_in_docker.sh -echo "running script ${RUNSCRIPT}" -${RUNSCRIPT} diff --git a/etc_upload/upload_config_for_ph_in_docker.sh b/etc_upload/upload_config_for_ph_in_docker.sh index e37215e..1eb0364 100644 --- a/etc_upload/upload_config_for_ph_in_docker.sh +++ b/etc_upload/upload_config_for_ph_in_docker.sh @@ -31,39 +31,11 @@ if [[ -n ${DOCKER_HOST} ]]; then DOCKER_HOSTNAME=${DOCKER_HOST//tcp:/} DOCKER_HOSTNAME=${DOCKER_HOSTNAME//:*[0-9]/} DOCKER_HOSTNAME=${DOCKER_HOSTNAME//\//} - echo "${APPNAME} on DOCKER_HOSTNAME=${DOCKER_HOSTNAME}" - export HOSTNAME=${DOCKER_HOSTNAME} - - # replace CONSUL_IP with docker-host-ip if consul-agent is local - CONSUL_HOST=${HOSTNAME} - CONSUL_IP=$(host ${CONSUL_HOST} | awk '/has address/ { print $4 ; exit }') - - echo "starting ${APPNAME} on HOSTNAME=${HOSTNAME} CONSUL_HOST=${CONSUL_HOST} CONSUL_IP=${CONSUL_IP}" - - docker run --name ${APPNAME} -d \ - -e HOSTNAME \ - --add-host consul:${CONSUL_IP} \ - ${APPNAME} + CONSUL_HOST=${DOCKER_HOSTNAME} else - export HOSTNAME=$(hostname --fqdn) - - # replace CONSUL_IP with docker-host-ip if consul-agent is local - CONSUL_HOST=${HOSTNAME} - CONSUL_IP=$(host ${CONSUL_HOST} | awk '/has address/ { print $4 ; exit }') - - echo "starting ${APPNAME} on HOSTNAME=${HOSTNAME} CONSUL_HOST=${CONSUL_HOST} CONSUL_IP=${CONSUL_IP}" - - BASEDIR=$(pwd) - TARGETDIR=/opt/app/${APPNAME} + CONSUL_HOST=devcnsl00.dcae.sic.research.att.com +fi - mkdir -p ${BASEDIR}/logs - mkdir -p ${BASEDIR}/etc_upload/logs +echo "uploading etc_upload/config.json for ${APPNAME} to CONSUL_HOST=${CONSUL_HOST}" - docker run --name ${APPNAME} -d \ - -e HOSTNAME \ - --add-host consul:${CONSUL_IP} \ - -v ${BASEDIR}/etc:${TARGETDIR}/etc \ - -v ${BASEDIR}/etc_upload:${TARGETDIR}/etc_upload \ - -v ${BASEDIR}/etc_upload/logs:${TARGETDIR}/logs \ - ${APPNAME} -fi +curl -X PUT -H 'Content-Type: application/json' --data-binary "$(cat etc_upload/config.json)" http://${CONSUL_HOST}:8500/v1/kv/${APPNAME} \ No newline at end of file diff --git a/policyhandler/config.py b/policyhandler/config.py index ea10167..9a4980a 100644 --- a/policyhandler/config.py +++ b/policyhandler/config.py @@ -26,7 +26,6 @@ import base64 import logging from .discovery import DiscoveryClient -from .onap.crypto import Cipher logging.basicConfig( filename='logs/policy_handler.log', \ @@ -38,14 +37,10 @@ class Config(object): """main config of the application""" CONFIG_FILE_PATH = "etc/config.json" LOGGER_CONFIG_FILE_PATH = "etc/common_logger.config" - UPLOAD_CONFIG_FILE_PATH = "etc_upload/config.json" SERVICE_NAME_POLICY_HANDLER = "policy_handler" FIELD_SYSTEM = "system" - FIELD_CONFIG_PWD = "config_pwd" FIELD_WSERVICE_PORT = "wservice_port" FIELD_POLICY_ENGINE = "policy_engine" - CRYPTED_FIELDS = ["ClientAuth", "Authorization", "config_pwd"] - config_pwd = "donottell150&$" wservice_port = 25577 _logger = logging.getLogger("policy_handler.config") config = None @@ -63,32 +58,6 @@ class Config(object): new_config = copy.deepcopy(new_config) Config.config.update(new_config) - @staticmethod - def decrypt_secret_value(field_name, field_value): - """decrypt the value of the secret field""" - if field_name in Config.CRYPTED_FIELDS and isinstance(field_value, basestring): - return Cipher.decrypt(Config.config_pwd, field_value) - return field_value - - @staticmethod - def encrypt_secret_value(field_name, field_value): - """encrypt the value of the secret field""" - if field_name in Config.CRYPTED_FIELDS and isinstance(field_value, basestring): - return Cipher.encrypt(Config.config_pwd, field_value) - return field_value - - @staticmethod - def update_tree_leaves(tree_element, func_on_leaf): - """traverse through json tree and apply function func_on_leaf to each leaf""" - if not tree_element: - return - - for field_name in tree_element: - field_value = func_on_leaf(field_name, tree_element[field_name]) - tree_element[field_name] = field_value - if isinstance(field_value, dict): - Config.update_tree_leaves(field_value, func_on_leaf) - @staticmethod def get_system_name(): """find the name of the policy-handler system @@ -112,9 +81,8 @@ class Config(object): Config._logger.debug("loaded config from discovery(%s): %s", \ discovery_key, json.dumps(new_config)) - Config.update_tree_leaves(new_config, Config.decrypt_secret_value) Config._logger.debug("config before merge from discovery: %s", json.dumps(Config.config)) - Config.merge(new_config) + Config.merge(new_config.get(Config.SERVICE_NAME_POLICY_HANDLER)) Config._logger.debug("merged config from discovery: %s", json.dumps(Config.config)) @staticmethod @@ -124,11 +92,8 @@ class Config(object): Config._logger.error("unexpected config: %s", Config.config) return - latest_config = copy.deepcopy(Config.config) - Config.update_tree_leaves(latest_config, Config.encrypt_secret_value) - discovery_key = Config.get_system_name() - latest_config = json.dumps(latest_config) + latest_config = json.dumps({Config.SERVICE_NAME_POLICY_HANDLER:Config.config}) DiscoveryClient.put_kv(discovery_key, latest_config) Config._logger.debug("uploaded config to discovery(%s): %s", \ discovery_key, latest_config) @@ -153,7 +118,6 @@ class Config(object): if logging_config: logging.config.dictConfig(logging_config) - Config.config_pwd = loaded_config.get(Config.FIELD_CONFIG_PWD, Config.config_pwd) Config.wservice_port = loaded_config.get(Config.FIELD_WSERVICE_PORT, Config.wservice_port) Config.merge(loaded_config.get(Config.SERVICE_NAME_POLICY_HANDLER)) return True diff --git a/policyhandler/deploy_handler.py b/policyhandler/deploy_handler.py index 02807f8..7d9c513 100644 --- a/policyhandler/deploy_handler.py +++ b/policyhandler/deploy_handler.py @@ -30,11 +30,10 @@ from .onap.audit import REQUEST_X_ECOMP_REQUESTID, Audit, AuditHttpCode class DeployHandler(object): """ deploy-handler """ _logger = logging.getLogger("policy_handler.deploy_handler") - _policy_update = '/policy_update' _lazy_inited = False _config = None _url = None - _path = None + _url_path = None _target_entity = None @staticmethod @@ -45,7 +44,7 @@ class DeployHandler(object): DeployHandler._lazy_inited = True DeployHandler._target_entity = Config.config["deploy_handler"] DeployHandler._url = DiscoveryClient.get_service_url(DeployHandler._target_entity) - DeployHandler._path = DeployHandler._url + DeployHandler._policy_update + DeployHandler._url_path = DeployHandler._url + '/policy' DeployHandler._logger.info("DeployHandler url(%s)", DeployHandler._url) @staticmethod @@ -53,24 +52,24 @@ class DeployHandler(object): """ post policy_updated message to deploy-handler """ DeployHandler._lazy_init() msg = {"latest_policies":latest_policies} - sub_aud = Audit(aud_parent=audit, targetEntity=DeployHandler._target_entity, \ - targetServiceName=DeployHandler._path) + sub_aud = Audit(aud_parent=audit, targetEntity=DeployHandler._target_entity, + targetServiceName=DeployHandler._url_path) headers = {REQUEST_X_ECOMP_REQUESTID : sub_aud.request_id} msg_str = json.dumps(msg) headers_str = json.dumps(headers) - log_line = "post to deployment-handler {0} msg={1} headers={2}".format(\ - DeployHandler._path, msg_str, headers_str) + log_line = "post to deployment-handler {0} msg={1} headers={2}".format( + DeployHandler._url_path, msg_str, headers_str) sub_aud.metrics_start(log_line) DeployHandler._logger.info(log_line) res = None try: - res = requests.post(DeployHandler._path, json=msg, headers=headers) + res = requests.post(DeployHandler._url_path, json=msg, headers=headers) except requests.exceptions.RequestException as ex: error_msg = "failed to post to deployment-handler {0} {1} msg={2} headers={3}" \ - .format(DeployHandler._path, str(ex), msg_str, headers_str) + .format(DeployHandler._url_path, str(ex), msg_str, headers_str) DeployHandler._logger.exception(error_msg) sub_aud.set_http_status_code(AuditHttpCode.SERVICE_UNAVAILABLE_ERROR.value) audit.set_http_status_code(AuditHttpCode.SERVICE_UNAVAILABLE_ERROR.value) @@ -80,9 +79,10 @@ class DeployHandler(object): sub_aud.set_http_status_code(res.status_code) audit.set_http_status_code(res.status_code) - sub_aud.metrics( \ + sub_aud.metrics( "response from deployment-handler to post {0}: {1} msg={2} text={3} headers={4}" \ - .format(DeployHandler._path, res.status_code, msg_str, res.text, res.request.headers)) + .format(DeployHandler._url_path, res.status_code, msg_str, res.text, + res.request.headers)) if res.status_code == requests.codes.ok: return res.json() diff --git a/policyhandler/onap/crypto.py b/policyhandler/onap/crypto.py deleted file mode 100644 index e2d58db..0000000 --- a/policyhandler/onap/crypto.py +++ /dev/null @@ -1,72 +0,0 @@ -"""ONAP specific encryption-decryption for passwords""" - -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. -# ================================================================================ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. - -import base64 -from Crypto.Cipher import AES -from Crypto.Protocol.KDF import PBKDF2 -from Crypto import Random - -class Cipher(object): - """class for AES-256 encryption and decryption of text using the salted password""" - KEY_SIZE = 32 # AES-256 - KDF_ITERATIONS = 16384 - AES_MODE = AES.MODE_CFB - - @staticmethod - def encrypt(password, plain_text): - """ - encrypt the :plain_text: into :cipher_text: using the password - - :cipher_text: formatted as pbkdf2_salt + init_vector + encrypt(:plain_text:) - then cipher_text is encoded as base64 to make it textable (non-binary) - - :pbkdf2_salt: has the fixed length of 32 (AES-256) - :init_vector: has the fixed length of AES.block_size - """ - pbkdf2_salt = Random.new().read(Cipher.KEY_SIZE) - init_vector = Random.new().read(AES.block_size) - derived_key = PBKDF2(password, pbkdf2_salt, Cipher.KEY_SIZE, Cipher.KDF_ITERATIONS) - - cipher = AES.new(derived_key, Cipher.AES_MODE, init_vector) - cipher_text = base64.b64encode(pbkdf2_salt + init_vector + cipher.encrypt(plain_text)) - return cipher_text - - @staticmethod - def decrypt(password, cipher_text): - """ - decrypt the :cipher_text: into :plain_text: using the password - - :cipher_text: is expected to be encoded as base64 to make it textable (non-binary) - inside of that it is expected to be formatted as - pbkdf2_salt + init_vector + encrypt(:plain_text:) - - :pbkdf2_salt: has the fixed length of 32 (AES-256) - :init_vector: has the fixed length of AES.block_size - """ - cipher_text = base64.b64decode(cipher_text) - pbkdf2_salt = cipher_text[: Cipher.KEY_SIZE] - init_vector = cipher_text[Cipher.KEY_SIZE : Cipher.KEY_SIZE + AES.block_size] - cipher_text = cipher_text[Cipher.KEY_SIZE + AES.block_size :] - derived_key = PBKDF2(password, pbkdf2_salt, Cipher.KEY_SIZE, Cipher.KDF_ITERATIONS) - - cipher = AES.new(derived_key, Cipher.AES_MODE, init_vector) - plain_text = cipher.decrypt(cipher_text).decode('utf-8') - return plain_text diff --git a/policyhandler/policy_engine.py b/policyhandler/policy_engine.py index 838ccc7..68e81cf 100644 --- a/policyhandler/policy_engine.py +++ b/policyhandler/policy_engine.py @@ -32,7 +32,7 @@ class PolicyNotificationHandler(NotificationHandler): _logger = logging.getLogger("policy_handler.policy_notification") def __init__(self, policy_updater): - scope_prefixes = [scope_prefix.replace(".", "[.]") \ + scope_prefixes = [scope_prefix.replace(".", "[.]") for scope_prefix in Config.config["scope_prefixes"]] self._policy_scopes = re.compile("(" + "|".join(scope_prefixes) + ")") PolicyNotificationHandler._logger.info("_policy_scopes %s", self._policy_scopes.pattern) @@ -43,13 +43,13 @@ class PolicyNotificationHandler(NotificationHandler): if not notification or not notification._loadedPolicies: return - policy_names = [loaded._policyName \ - for loaded in notification._loadedPolicies \ - if self._policy_scopes.match(loaded._policyName)] + policy_names = [loaded._policyName + for loaded in notification._loadedPolicies + if self._policy_scopes.match(loaded._policyName)] if not policy_names: - PolicyNotificationHandler._logger.info("no policy updated for scopes %s", \ - self._policy_scopes.pattern) + PolicyNotificationHandler._logger.info("no policy updated for scopes %s", + self._policy_scopes.pattern) return audit = Audit(req_message="notificationReceived from PDP") @@ -89,11 +89,13 @@ class PolicyEngineClient(object): sub_aud = Audit(aud_parent=audit) sub_aud.metrics_start("create client to PDP") PolicyEngineConfig.save_to_file() - PolicyEngineClient._policy_engine = PolicyEngine(PolicyEngineConfig.PATH_TO_PROPERTIES, \ - scheme=NotificationScheme.AUTO_ALL_NOTIFICATIONS.name,\ - handler=PolicyEngineClient._pdp_notification_handler) + PolicyEngineClient._policy_engine = PolicyEngine( + PolicyEngineConfig.PATH_TO_PROPERTIES, + scheme=NotificationScheme.AUTO_ALL_NOTIFICATIONS.name, + handler=PolicyEngineClient._pdp_notification_handler + ) sub_aud.metrics("created client to PDP") - seed_scope = Config.config["scope_prefixes"][0] + ".*" + seed_scope = ".*" PolicyEngineClient._policy_engine.getConfig(policyName=seed_scope) sub_aud.metrics("seeded client by PDP.getConfig for policyName={0}".format(seed_scope)) diff --git a/policyhandler/policy_handler.py b/policyhandler/policy_handler.py index 10633cd..50d59bc 100644 --- a/policyhandler/policy_handler.py +++ b/policyhandler/policy_handler.py @@ -60,28 +60,5 @@ def run_policy_handler(): PolicyEngineClient.run() PolicyWeb.run() -def upload_config_to_discovery(): - """read the config from file and upload it to discovery""" - logger = logging.getLogger("policy_handler") - sys.stdout = LogWriter(logger.info) - sys.stderr = LogWriter(logger.error) - - Config.load_from_file() - - if not Config.load_from_file(Config.UPLOAD_CONFIG_FILE_PATH): - logger.info("not found config %s", Config.UPLOAD_CONFIG_FILE_PATH) - return - - logger.info("========== upload_config_to_discovery ==========") - Config.upload_to_discovery() - - logger.info("========== upload_config_to_discovery - get it back ==========") - Config.config = None - Config.load_from_file() - Config.discover() - logger.info("========== upload_config_to_discovery - done ==========") - return True - if __name__ == "__main__": - if not upload_config_to_discovery(): - run_policy_handler() + run_policy_handler() diff --git a/requirements.txt b/requirements.txt index cafb36d..43e8bf7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ CherryPy>=10.2.2 enum34>=1.1.6 future>=0.16.0 -pycrypto>=2.6.1 requests>=2.13.0,<3.0.0 six>=1.10.0 websocket-client>=0.40.0 diff --git a/setup.py b/setup.py index adfcf98..d39a1c2 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ from setuptools import setup setup( name='policyhandler', description='DCAE-Controller policy-handler to communicate with policy-engine', - version="0.0.1", + version="1.0.0", author='Alex Shatov', packages=['policyhandler'], zip_safe=False, @@ -32,9 +32,14 @@ setup( "CherryPy>=10.2.2", "enum34>=1.1.6", "future>=0.16.0", - "pycrypto>=2.6.1", "requests>=2.13.0", "six>=1.10.0", "websocket-client>=0.40.0" + ], + keywords='policy dcae controller', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2.7' ] ) diff --git a/start_ph_in_docker.sh b/start_ph_in_docker.sh index 13b94df..e94bdb0 100644 --- a/start_ph_in_docker.sh +++ b/start_ph_in_docker.sh @@ -33,13 +33,13 @@ if [[ -n ${DOCKER_HOST} ]]; then # replace CONSUL_IP with docker-host-ip if consul-agent is local CONSUL_HOST=${HOSTNAME} CONSUL_IP=$(host ${CONSUL_HOST} | awk '/has address/ { print $4 ; exit }') + if [ "0${CONSUL_IP}" = "0" ]; then + CONSUL_IP=${CONSUL_HOST} + fi echo "starting ${APPNAME} on HOSTNAME=${HOSTNAME} CONSUL_HOST=${CONSUL_HOST} CONSUL_IP=${CONSUL_IP}" docker run --name ${APPNAME} -d -e HOSTNAME --add-host consul:${CONSUL_IP} -P ${APPNAME} - - # cd logs - # docker cp ${APPNAME}:/opt/app/${APPNAME}/logs . else export HOSTNAME=$(hostname --fqdn) @@ -61,4 +61,10 @@ else -v ${BASEDIR}/logs:${TARGETDIR}/logs \ -p 25577:25577 \ ${APPNAME} -fi \ No newline at end of file +fi + +docker ps -a | grep ${APPNAME} +echo "--- --- --- --- ---" +docker exec ${APPNAME} sh -c "whoami;pwd;set;ls -la;ls -l logs" +echo "copy logs: docker cp ${APPNAME}:/opt/app/${APPNAME}/logs logs/" +echo "export logs: docker logs ${APPNAME} > logs/"'$(date +%Y_%m%d-%H%M%S)_'"${APPNAME}.log 2>&1" -- cgit 1.2.3-korg