| #!/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/cloudstack-utils.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(value): | |
| cmd = [ | |
| "java", | |
| "-classpath", | |
| '"' + self.encryptionJarPath + '"', | |
| "com.cloud.utils.crypt.EncryptionCLI", | |
| "-i", | |
| '"' + value + '"', | |
| "-p", | |
| '"' + self.mgmtsecretkey + '"', | |
| ] | |
| 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() |