From 920b9ff54e4b75973b27438158030c5a4736ffc6 Mon Sep 17 00:00:00 2001 From: Ikram Ikramullah Date: Thu, 23 Jan 2020 11:53:16 -0500 Subject: Encryption/Decryption utility Putting in the encryption/decryption utility. Hit cipher-util inside the virutalenv to see its usage. The work is still in progress. Issue-ID: OPTFRA-683 Signed-off-by: dhebeha Change-Id: I7a4845c1318db8d14f1efbb0f98e785adf214e06 --- api_paste.ini | 26 ++++++++ conductor.conf | 3 + .../api/adapters/aaf/aaf_authentication.py | 6 +- conductor/conductor/cmd/encryptionUtil.py | 54 +++++++++++++++ conductor/conductor/common/music/api.py | 14 ++-- .../conductor/common/utils/basic_auth_util.py | 2 +- conductor/conductor/common/utils/cipherUtils.py | 77 ++++++++++++++++++++++ .../data/plugins/inventory_provider/aai.py | 5 +- .../data/plugins/service_controller/sdnc.py | 3 +- .../data/plugins/vim_controller/multicloud.py | 2 +- .../data/plugins/inventory_provider/test_aai.py | 2 +- conductor/conductor/tests/unit/music/test_api.py | 2 +- conductor/requirements.txt | 3 +- conductor/setup.cfg | 1 + preload_secrets.yaml | 10 +-- 15 files changed, 187 insertions(+), 23 deletions(-) create mode 100644 api_paste.ini create mode 100644 conductor/conductor/cmd/encryptionUtil.py create mode 100644 conductor/conductor/common/utils/cipherUtils.py diff --git a/api_paste.ini b/api_paste.ini new file mode 100644 index 0000000..4299f46 --- /dev/null +++ b/api_paste.ini @@ -0,0 +1,26 @@ +# Conductor API WSGI Pipeline +# Define the filters that make up the pipeline for processing WSGI requests +# Note: This pipeline is PasteDeploy's term rather than Conductor's pipeline +# used for processing samples + +# Remove authtoken from the pipeline if you don't want to use keystone authentication +[pipeline:main] +pipeline = cors http_proxy_to_wsgi api-server +#pipeline = cors http_proxy_to_wsgi request_id authtoken api-server + +[app:api-server] +paste.app_factory = conductor.api.app:app_factory + +#[filter:authtoken] +#paste.filter_factory = keystonemiddleware.auth_token:filter_factory + +#[filter:request_id] +#paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = conductor + +[filter:http_proxy_to_wsgi] +paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.factory +oslo_config_project = conductor diff --git a/conductor.conf b/conductor.conf index b4f09b1..79ec774 100755 --- a/conductor.conf +++ b/conductor.conf @@ -131,6 +131,9 @@ #fatal_deprecations = false +[auth] +aapkey = h@ss3crtky400fdntc#001 + [aaf_api] # diff --git a/conductor/conductor/api/adapters/aaf/aaf_authentication.py b/conductor/conductor/api/adapters/aaf/aaf_authentication.py index a85ac11..fb0b9ab 100644 --- a/conductor/conductor/api/adapters/aaf/aaf_authentication.py +++ b/conductor/conductor/api/adapters/aaf/aaf_authentication.py @@ -24,7 +24,7 @@ import os from conductor.common import rest from conductor.i18n import _LE, _LI -from conductor import __file__ as conductor_root +from conductor.common.utils import cipherUtils from oslo_log import log LOG = log.getLogger(__name__) @@ -83,7 +83,7 @@ def clear_cache(): def authenticate(uid, passwd): aafUser = None username = CONF.conductor_api.username - password = CONF.conductor_api.password + password = cipherUtils.AESCipher.get_instance().decrypt(CONF.conductor_api.password) if username == uid and password == passwd: aafUser = CONF.aaf_api.aaf_conductor_user else: @@ -159,7 +159,7 @@ def remote_api(aafUser): "server_url": server_url, "retries": CONF.aaf_api.aaf_retries, "username": CONF.aaf_api.username, - "password": CONF.aaf_api.password, + "password": cipherUtils.AESCipher.get_instance().decrypt(CONF.aaf_api.password), "log_debug": LOG.debug, "read_timeout": CONF.aaf_api.aaf_timeout, "cert_file": CONF.aaf_api.aaf_cert_file, diff --git a/conductor/conductor/cmd/encryptionUtil.py b/conductor/conductor/cmd/encryptionUtil.py new file mode 100644 index 0000000..179d366 --- /dev/null +++ b/conductor/conductor/cmd/encryptionUtil.py @@ -0,0 +1,54 @@ +# +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2018 AT&T Intellectual Property +# +# 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. +# +# ------------------------------------------------------------------------- +# + +import sys +from conductor.common.utils import cipherUtils + + +def main(): + + if len(sys.argv) != 4: + print("Invalid input - usage --> (options(encrypt/decrypt) input-value with-key)") + return + + enc_dec = sys.argv[1] + valid_option_values = ['encrypt', 'decrypt'] + if enc_dec not in valid_option_values: + print("Invalid input - usage --> (options(encrypt/decrypt) input-value with-key)") + print("Option value can only be one of {}".format(valid_option_values)) + print("You entered '{}'".format(enc_dec)) + return + + input_string = sys.argv[2] + with_key = sys.argv[3] + + print("You've requested '{}' to be '{}ed' using key '{}'".format(input_string, enc_dec, with_key)) + print("You can always perform the reverse operation (encrypt/decrypt) using the same key" + "to be certain you get the same results back'") + + util = cipherUtils.AESCipher.get_instance(with_key) + #util = CipherUtil.AESCipher(with_key) + + if enc_dec.lower() == 'encrypt': + result = util.encrypt(input_string) + else: + result = util.decrypt(input_string) + + print("Your result: {}".format(result)) + diff --git a/conductor/conductor/common/music/api.py b/conductor/conductor/common/music/api.py index 0ca4301..5929b58 100644 --- a/conductor/conductor/common/music/api.py +++ b/conductor/conductor/common/music/api.py @@ -30,6 +30,7 @@ from oslo_log import log from conductor.common import rest from conductor.common.utils import basic_auth_util from conductor.i18n import _LE, _LI # pylint: disable=W0212 +from conductor.common.utils import cipherUtils LOG = log.getLogger(__name__) @@ -137,22 +138,23 @@ class MusicAPI(object): } self.rest = rest.REST(**kwargs) + music_pwd = cipherUtils.AESCipher.get_instance().decrypt(CONF.music_api.aafpass) # Set one parameter for connection mode # Currently depend on music version - if (CONF.music_api.enable_https_mode): + if CONF.music_api.enable_https_mode: self.rest.server_url = 'https://{}:{}/{}'.format( host, port, version, path.rstrip('/').lstrip('/')) self.rest.session.verify = CONF.music_api.certificate_authority_bundle_file - if(CONF.music_api.music_new_version): - MUSIC_version = CONF.music_api.music_version.split(".") + if CONF.music_api.music_new_version: + music_version = CONF.music_api.music_version.split(".") self.rest.session.headers['content-type'] = 'application/json' - self.rest.session.headers['X-minorVersion'] = MUSIC_version[1] - self.rest.session.headers['X-patchVersion'] = MUSIC_version[2] + self.rest.session.headers['X-minorVersion'] = music_version[1] + self.rest.session.headers['X-patchVersion'] = music_version[2] self.rest.session.headers['ns'] = CONF.music_api.aafns self.rest.session.headers['userId'] = CONF.music_api.aafuser - self.rest.session.headers['password'] = CONF.music_api.aafpass + self.rest.session.headers['password'] = music_pwd self.rest.session.headers['Authorization'] = basic_auth_util.encode(CONF.music_api.aafuser, CONF.music_api.aafpass) diff --git a/conductor/conductor/common/utils/basic_auth_util.py b/conductor/conductor/common/utils/basic_auth_util.py index a94418f..d4cdbac 100644 --- a/conductor/conductor/common/utils/basic_auth_util.py +++ b/conductor/conductor/common/utils/basic_auth_util.py @@ -28,7 +28,7 @@ LOG = log.getLogger(__name__) def encode(user_id, password): """ Provide the basic authencation encoded value in an 'Authorization' Header """ - user_pass = user_id + ':' + password + user_pass = str(user_id) + ":" + str(password) base64_val = base64.b64encode(user_pass) authorization_val = _LE("Basic {}".format(base64_val)) diff --git a/conductor/conductor/common/utils/cipherUtils.py b/conductor/conductor/common/utils/cipherUtils.py new file mode 100644 index 0000000..6ee6c58 --- /dev/null +++ b/conductor/conductor/common/utils/cipherUtils.py @@ -0,0 +1,77 @@ +# +# ------------------------------------------------------------------------- +# Copyright (c) 2015-2017 AT&T Intellectual Property +# +# 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. +# +# ------------------------------------------------------------------------- +# + +import base64 +import hashlib +from Crypto import Random +from Crypto.Cipher import AES +from oslo_config import cfg + +CONF = cfg.CONF + +cipher_opts = [ + cfg.StrOpt('appkey', + default='ch00se@g003ntropy', + help='Master key to secure other secrets') +] + +CONF.register_opts(cipher_opts, group='auth') + + +class AESCipher(object): + __instance = None + + @staticmethod + def get_instance(key = None): + if AESCipher.__instance is None: + print ('Creating the singleton instance') + AESCipher(key) + return AESCipher.__instance + + def __init__(self, key=None): + if AESCipher.__instance is not None: + raise Exception("This class is a singleton!") + else: + AESCipher.__instance = self + + self.bs = 32 + if key is None: + key = CONF.auth.appkey.encode() + + self.key = hashlib.sha256(key.encode()).digest() + + def encrypt(self, raw): + raw = self._pad(raw) + iv = Random.new().read(AES.block_size) + cipher = AES.new(self.key, AES.MODE_CBC, iv) + return base64.b64encode(iv + cipher.encrypt(raw)) + + def decrypt(self, enc): + enc = base64.b64decode(enc) + iv = enc[:AES.block_size] + cipher = AES.new(self.key, AES.MODE_CBC, iv) + return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8') + + def _pad(self, s): + return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs) + + @staticmethod + def _unpad(s): + return s[:-ord(s[len(s)-1:])] + diff --git a/conductor/conductor/data/plugins/inventory_provider/aai.py b/conductor/conductor/data/plugins/inventory_provider/aai.py index 26b390a..f49d526 100644 --- a/conductor/conductor/data/plugins/inventory_provider/aai.py +++ b/conductor/conductor/data/plugins/inventory_provider/aai.py @@ -23,11 +23,10 @@ import uuid import copy import json -from oslo_config import cfg -from oslo_log import log from conductor.common import rest from conductor.data.plugins import constants +from conductor.common.utils import cipherUtils from conductor.data.plugins.inventory_provider import base from conductor.data.plugins.inventory_provider import hpa_utils from conductor.data.plugins.triage_translator.triage_translator import TraigeTranslator @@ -111,7 +110,7 @@ class AAI(base.InventoryProviderBase): self.timeout = self.conf.aai.aai_rest_timeout self.retries = self.conf.aai.aai_retries self.username = self.conf.aai.username - self.password = self.conf.aai.password + self.password = cipherUtils.AESCipher.get_instance().decrypt(self.conf.aai.password) self.triage_translator=TraigeTranslator() # Cache is initially empty diff --git a/conductor/conductor/data/plugins/service_controller/sdnc.py b/conductor/conductor/data/plugins/service_controller/sdnc.py index 5e4e8a0..1571b41 100644 --- a/conductor/conductor/data/plugins/service_controller/sdnc.py +++ b/conductor/conductor/data/plugins/service_controller/sdnc.py @@ -23,6 +23,7 @@ from oslo_config import cfg from oslo_log import log from conductor.common import rest +from conductor.common.utils import cipherUtils from conductor.data.plugins.service_controller import base from conductor.i18n import _LE @@ -66,7 +67,7 @@ class SDNC(base.ServiceControllerBase): self.conf = CONF self.base = self.conf.sdnc.server_url.rstrip('/') - self.password = self.conf.sdnc.password + self.password = cipherUtils.AESCipher.get_instance().decrypt(self.conf.sdnc.password) self.timeout = self.conf.sdnc.sdnc_rest_timeout self.verify = False self.retries = self.conf.sdnc.sdnc_retries diff --git a/conductor/conductor/data/plugins/vim_controller/multicloud.py b/conductor/conductor/data/plugins/vim_controller/multicloud.py index 5c2b5f7..b5c7a66 100644 --- a/conductor/conductor/data/plugins/vim_controller/multicloud.py +++ b/conductor/conductor/data/plugins/vim_controller/multicloud.py @@ -115,7 +115,7 @@ class MULTICLOUD(base.VimControllerBase): "read_timeout": self.timeout, } self.rest = rest.REST(**kwargs) - if(self.conf.multicloud.enable_https_mode): + if self.conf.multicloud.enable_https_mode: self.rest.server_url = self.base[:4]+'s'+self.base[4:] self.rest.session.verify =self.conf.multicloud.certificate_authority_bundle_file diff --git a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py index 9d1245d..5b9984a 100644 --- a/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py +++ b/conductor/conductor/tests/unit/data/plugins/inventory_provider/test_aai.py @@ -30,7 +30,7 @@ from oslo_config import cfg class TestAAI(unittest.TestCase): def setUp(self): - + cfg.CONF.set_override('password', '4HyU6sI+Tw0YMXgSHr5sJ5C0UTkeBaxXoxQqWuSVFugls7sQnaAXp4zMfJ8FKFrH', 'aai') CONF = cfg.CONF CONF.register_opts(aai.AAI_OPTS, group='aai') self.conf = CONF diff --git a/conductor/conductor/tests/unit/music/test_api.py b/conductor/conductor/tests/unit/music/test_api.py index 90bd57d..285d0d7 100644 --- a/conductor/conductor/tests/unit/music/test_api.py +++ b/conductor/conductor/tests/unit/music/test_api.py @@ -23,12 +23,12 @@ from conductor.common import rest from conductor.common.music.api import MusicAPI from oslo_config import cfg - class TestMusicApi(unittest.TestCase): def setUp(self): cfg.CONF.set_override('debug', True, 'music_api') cfg.CONF.set_override('certificate_authority_bundle_file', '../AAF_RootCA.cer', 'music_api') + cfg.CONF.set_override('aafpass', 'U5AudaTTC/DYQ29Q7qiXx/iwsgwV+dV7v7/Q5TDBh1URPwqAGECnbVmUkOynLjTY', 'music_api') self.mock_lock_id = mock.patch.object(MusicAPI, '_lock_id_create', return_value='12345678') self.mock_lock_acquire = mock.patch.object(MusicAPI, diff --git a/conductor/requirements.txt b/conductor/requirements.txt index d6d413d..6ad13e2 100644 --- a/conductor/requirements.txt +++ b/conductor/requirements.txt @@ -25,4 +25,5 @@ stevedore>=1.9.0 # Apache-2.0, also required by oslo.config WebOb>=1.2.3 # MIT onapsmsclient>=0.0.4 Flask>=0.11.1 -prometheus-client>=0.3.1 \ No newline at end of file +prometheus-client>=0.3.1 +pycrypto==2.6.1 diff --git a/conductor/setup.cfg b/conductor/setup.cfg index 5152285..9d2298d 100644 --- a/conductor/setup.cfg +++ b/conductor/setup.cfg @@ -63,6 +63,7 @@ console_scripts = conductor-data = conductor.cmd.data:main conductor-solver = conductor.cmd.solver:main conductor-reservation = conductor.cmd.reservation:main + cipher-util = conductor.cmd.encryptionUtil:main conductor.inventory_provider.plugin = aai = conductor.data.plugins.inventory_provider.aai:AAI diff --git a/preload_secrets.yaml b/preload_secrets.yaml index 3ec1b92..62a00f2 100755 --- a/preload_secrets.yaml +++ b/preload_secrets.yaml @@ -6,22 +6,22 @@ secrets: - name: aai values: username: oof@oof.onap.org - password: demo123456! + password: 4HyU6sI+Tw0YMXgSHr5sJ5C0UTkeBaxXoxQqWuSVFugls7sQnaAXp4zMfJ8FKFrH - name: conductor_api values: username: admin1 - password: plan.15 + password: /90ON7wZM2SfItrVXix57TpCyMMR/EUxrhE9Vbo62yJyNfcrpEfsYEZKqTOXHmkn - name: sdnc values: username: admin - password: Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U + password: jI4D86X3lZIZILHLpHIWVsJeYODWpcEHlkfswE3pHF0UKjwp36euiwILMyV1jmyNjFF37we3n9VKZFc2UYO6Xsrfe2wBfwhod9Ft4rZEqFo - name: music_api values: aafuser: conductor - aafpass: c0nduct0r + aafpass: U5AudaTTC/DYQ29Q7qiXx/iwsgwV+dV7v7/Q5TDBh1URPwqAGECnbVmUkOynLjTY aafns: conductor - name: aaf_api values: username: aaf_admin@people.osaaf.org - password: demo123456! + password: IE4lBg4NmcIikB7RLjnXbyVeU2jxuKUWqX1ZfEzCcBAe0wDV87xFGTYTNoTzptXn aaf_conductor_user: oof@oof.onap.org -- cgit 1.2.3-korg