|  | #!/usr/bin/python | 
|  | # -*- 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 commands | 
|  | 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 | 
|  |  | 
|  | 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.0.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 self.dbDotProperties.has_key(key): | 
|  | (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 not self.dbDotProperties.has_key(key): | 
|  | 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, 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 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() | 
|  |  |