#!/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()