| #!/usr/bin/python3 |
| # -*- coding: utf-8 -*- |
| |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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 os |
| import sys |
| import subprocess |
| import glob |
| from random import choice |
| import string |
| from optparse import OptionParser |
| import subprocess |
| import shutil |
| |
| # squelch mysqldb spurious warnings |
| import warnings |
| warnings.simplefilter('ignore') |
| # ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ---- |
| # ---- We do this so cloud_utils can be looked up in the following order: |
| # ---- 1) Sources directory |
| # ---- 2) waf configured PYTHONDIR |
| # ---- 3) System Python path |
| for pythonpath in ( |
| "@PYTHONDIR@", |
| os.path.join(os.path.dirname(__file__),os.path.pardir,os.path.pardir,"python","lib"), |
| ): |
| if os.path.isdir(pythonpath): sys.path.insert(0,pythonpath) |
| # ---- End snippet of code ---- |
| |
| def runCmd(cmds): |
| process = subprocess.Popen(' '.join(cmds), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| stdout, stderr = process.communicate() |
| if process.returncode != 0: |
| raise Exception(stderr) |
| return stdout.decode('utf-8') |
| |
| class DBDeployer(object): |
| parser = None |
| options = None |
| args = None |
| isDebug = False |
| mgmtsecretkey = None |
| dbsecretkey = None |
| encryptiontype = None |
| dbConfPath = r"@MSCONF@" |
| dbDotProperties = {} |
| dbDotPropertiesIndex = 0 |
| encryptionKeyFile = '@MSCONF@/key' |
| encryptionJarPath = '@COMMONLIBDIR@/lib/jasypt-1.9.3.jar' |
| success = False |
| magicString = 'This_is_a_magic_string_i_think_no_one_will_duplicate' |
| |
| def preRun(self): |
| def backUpDbDotProperties(): |
| dbpPath = os.path.join(self.dbConfPath, 'db.properties') |
| copyPath = os.path.join(self.dbConfPath, 'db.properties.origin') |
| |
| if os.path.isfile(dbpPath): |
| shutil.copy2(dbpPath, copyPath) |
| |
| backUpDbDotProperties() |
| |
| def postRun(self): |
| def cleanOrRecoverDbDotProperties(): |
| dbpPath = os.path.join(self.dbConfPath, 'db.properties') |
| copyPath = os.path.join(self.dbConfPath, 'db.properties.origin') |
| if os.path.isfile(copyPath): |
| if not self.success: |
| shutil.copy2(copyPath, dbpPath) |
| os.remove(copyPath) |
| |
| cleanOrRecoverDbDotProperties() |
| |
| |
| def info(self, msg, result=None): |
| output = "" |
| if msg is not None: |
| output = "%-80s"%msg |
| |
| if result is True: |
| output += "[ \033[92m%-2s\033[0m ]\n"%"OK" |
| elif result is False: |
| output += "[ \033[91m%-6s\033[0m ]\n"%"FAILED" |
| sys.stdout.write(output) |
| sys.stdout.flush() |
| |
| def debug(self, msg): |
| msg = "DEBUG:%s"%msg |
| sys.stdout.write(msg) |
| sys.stdout.flush() |
| |
| def putDbProperty(self, key, value): |
| if key in self.dbDotProperties: |
| (oldValue, index) = self.dbDotProperties[key] |
| self.dbDotProperties[key] = (value, index) |
| else: |
| self.dbDotProperties[key] = (value, self.dbDotPropertiesIndex) |
| self.dbDotPropertiesIndex += 1 |
| |
| def getDbProperty(self, key): |
| if key not in self.dbDotProperties: |
| return None |
| (value, index) = self.dbDotProperties[key] |
| return value |
| |
| def errorAndExit(self, msg): |
| self.postRun() |
| err = '''\n\nWe apologize for below error: |
| *************************************************************** |
| %s |
| *************************************************************** |
| Please run: |
| |
| cloud-setup-encryption -h |
| |
| for full help |
| ''' % msg |
| sys.stderr.write(err) |
| sys.stderr.flush() |
| sys.exit(1) |
| |
| def prepareDBFiles(self): |
| def prepareDBDotProperties(): |
| dbpPath = os.path.join(self.dbConfPath, 'db.properties') |
| dbproperties = file(dbpPath).read().splitlines() |
| newdbp = [] |
| emptyLine = 0 |
| for line in dbproperties: |
| passed = False |
| line = line.strip() |
| if line.startswith("#"): key = line; value = ''; passed = True |
| if line == '' or line == '\n': key = self.magicString + str(emptyLine); value = ''; emptyLine += 1; passed = True |
| |
| try: |
| if not passed: |
| (key, value) = line.split('=', 1) |
| except Exception as e: |
| err = '''Wrong format in %s (%s): |
| Besides comments beginning "#" and empty line, all key-value pairs must be in formula of |
| key=value |
| for example: |
| db.cloud.username = cloud |
| ''' % (dbpPath, line) |
| self.errorAndExit(err) |
| self.putDbProperty(key, value) |
| self.info("Preparing %s"%dbpPath, True) |
| |
| prepareDBDotProperties() |
| |
| def finalize(self): |
| def finalizeDbProperties(): |
| entries = [] |
| for key in list(self.dbDotProperties.keys()): |
| (value, index) = self.dbDotProperties[key] |
| if key.startswith("#"): |
| entries.insert(index, key) |
| elif key.startswith(self.magicString): |
| entries.insert(index, '') |
| else: |
| entries.insert(index, "%s=%s"%(key, value)) |
| file(os.path.join(self.dbConfPath, 'db.properties'), 'w').write('\n'.join(entries)) |
| |
| self.info("Finalizing setup ...", None) |
| finalizeDbProperties() |
| self.info(None, True) |
| self.success = True # At here, we have done successfully and nothing more after this flag is set |
| |
| def processEncryptionStuff(self): |
| def encrypt(input): |
| cmd = ['java','-classpath',self.encryptionJarPath,'org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI', 'encrypt.sh', 'input=\'%s\''%input, 'password=%s'%self.mgmtsecretkey,'verbose=false'] |
| return runCmd(cmd).strip('\n') |
| |
| def saveMgmtServerSecretKey(): |
| if self.encryptiontype == 'file': |
| file(self.encryptionKeyFile, 'w').write(self.mgmtsecretkey) |
| |
| def formatEncryptResult(value): |
| return 'ENC(%s)'%value |
| |
| def encryptDBSecretKey(): |
| self.putDbProperty('db.cloud.encrypt.secret', formatEncryptResult(encrypt(self.dbsecretkey))) |
| |
| def encryptDBPassword(): |
| dbPassword = self.getDbProperty('db.cloud.password') |
| if dbPassword == '': return # Don't encrypt empty password |
| if dbPassword == None: self.errorAndExit('Cannot find db.cloud.password in %s'%os.path.join(self.dbConfPath, 'db.properties')) |
| self.putDbProperty('db.cloud.password', formatEncryptResult(encrypt(dbPassword))) |
| |
| usagePassword = self.getDbProperty('db.usage.password') |
| if usagePassword == '': return # Don't encrypt empty password |
| if usagePassword == None: self.errorAndExit('Cannot find db.usage.password in %s'%os.path.join(self.dbConfPath, 'db.properties')) |
| self.putDbProperty('db.usage.password', formatEncryptResult(encrypt(usagePassword))) |
| |
| self.info("Processing encryption ...", None) |
| self.putDbProperty("db.cloud.encryption.type", self.encryptiontype) |
| saveMgmtServerSecretKey() |
| encryptDBSecretKey() |
| encryptDBPassword() |
| self.info(None, True) |
| |
| def parseOptions(self): |
| def parseOtherOptions(): |
| self.encryptiontype = self.options.encryptiontype |
| self.mgmtsecretkey = self.options.mgmtsecretkey |
| self.dbsecretkey = self.options.dbsecretkey |
| self.isDebug = self.options.debug |
| |
| |
| def validateParameters(): |
| if self.encryptiontype != 'file' and self.encryptiontype != 'web': |
| self.errorAndExit('Wrong encryption type %s, --encrypt-type can only be "file" or "web'%self.encryptiontype) |
| |
| #---------------------- option parsing and command line checks ------------------------ |
| usage = """%prog [-e ENCRYPTIONTYPE] [-m MGMTSECRETKEY] [-k DBSECRETKEY] [--debug] |
| |
| This command sets up the CloudStack Encryption. |
| |
| """ |
| self.parser = OptionParser(usage=usage) |
| self.parser.add_option("-v", "--debug", action="store_true", dest="debug", default=False, |
| help="If enabled, print the commands it will run as they run") |
| self.parser.add_option("-e", "--encrypt-type", action="store", type="string", dest="encryptiontype", default="file", |
| help="Encryption method used for db password encryption. Valid values are file, web. Default is file.") |
| self.parser.add_option("-m", "--managementserver-secretkey", action="store", type="string", dest="mgmtsecretkey", default="password", |
| help="Secret key used to encrypt confidential parameters in db.properties. A string, default is password") |
| self.parser.add_option("-k", "--database-secretkey", action="store", type="string", dest="dbsecretkey", default="password", |
| help="Secret key used to encrypt sensitive database values. A string, default is password") |
| |
| (self.options, self.args) = self.parser.parse_args() |
| parseOtherOptions() |
| validateParameters() |
| |
| def run(self): |
| try: |
| self.preRun() |
| self.parseOptions() |
| self.prepareDBFiles() |
| self.processEncryptionStuff() |
| self.finalize() |
| finally: |
| self.postRun() |
| |
| print('') |
| print("CloudStack has successfully setup Encryption") |
| print('') |
| |
| if __name__ == "__main__": |
| o = DBDeployer() |
| o.run() |
| |