|
| 1 | +#!/usr/bin/python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +import os |
| 5 | +import sys |
| 6 | +import subprocess |
| 7 | +import glob |
| 8 | +from random import choice |
| 9 | +import string |
| 10 | +from optparse import OptionParser |
| 11 | +import commands |
| 12 | +import shutil |
| 13 | + |
| 14 | +# squelch mysqldb spurious warnings |
| 15 | +import warnings |
| 16 | +warnings.simplefilter('ignore') |
| 17 | +# ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ---- |
| 18 | +# ---- We do this so cloud_utils can be looked up in the following order: |
| 19 | +# ---- 1) Sources directory |
| 20 | +# ---- 2) waf configured PYTHONDIR |
| 21 | +# ---- 3) System Python path |
| 22 | +for pythonpath in ( |
| 23 | + "@PYTHONDIR@", |
| 24 | + os.path.join(os.path.dirname(__file__),os.path.pardir,os.path.pardir,"python","lib"), |
| 25 | + ): |
| 26 | + if os.path.isdir(pythonpath): sys.path.insert(0,pythonpath) |
| 27 | +# ---- End snippet of code ---- |
| 28 | + |
| 29 | +def runCmd(cmds): |
| 30 | + process = subprocess.Popen(' '.join(cmds), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| 31 | + stdout, stderr = process.communicate() |
| 32 | + if process.returncode != 0: |
| 33 | + raise Exception(stderr) |
| 34 | + return stdout |
| 35 | + |
| 36 | +class DBDeployer(object): |
| 37 | + parser = None |
| 38 | + options = None |
| 39 | + args = None |
| 40 | + isDebug = False |
| 41 | + mgmtsecretkey = None |
| 42 | + dbsecretkey = None |
| 43 | + encryptiontype = None |
| 44 | + dbConfPath = r"@MSCONF@" |
| 45 | + dbDotProperties = {} |
| 46 | + dbDotPropertiesIndex = 0 |
| 47 | + encryptionKeyFile = '@MSCONF@/key' |
| 48 | + encryptionJarPath = '@JAVADIR@/cloud-jasypt-1.8.jar' |
| 49 | + success = False |
| 50 | + magicString = 'This_is_a_magic_string_i_think_no_one_will_duplicate' |
| 51 | + |
| 52 | + def preRun(self): |
| 53 | + def backUpDbDotProperties(): |
| 54 | + dbpPath = os.path.join(self.dbConfPath, 'db.properties') |
| 55 | + copyPath = os.path.join(self.dbConfPath, 'db.properties.origin') |
| 56 | + |
| 57 | + if os.path.isfile(dbpPath): |
| 58 | + shutil.copy2(dbpPath, copyPath) |
| 59 | + |
| 60 | + backUpDbDotProperties() |
| 61 | + |
| 62 | + def postRun(self): |
| 63 | + def cleanOrRecoverDbDotProperties(): |
| 64 | + dbpPath = os.path.join(self.dbConfPath, 'db.properties') |
| 65 | + copyPath = os.path.join(self.dbConfPath, 'db.properties.origin') |
| 66 | + if os.path.isfile(copyPath): |
| 67 | + if not self.success: |
| 68 | + shutil.copy2(copyPath, dbpPath) |
| 69 | + os.remove(copyPath) |
| 70 | + |
| 71 | + cleanOrRecoverDbDotProperties() |
| 72 | + |
| 73 | + |
| 74 | + def info(self, msg, result=None): |
| 75 | + output = "" |
| 76 | + if msg is not None: |
| 77 | + output = "%-80s"%msg |
| 78 | + |
| 79 | + if result is True: |
| 80 | + output += "[ \033[92m%-2s\033[0m ]\n"%"OK" |
| 81 | + elif result is False: |
| 82 | + output += "[ \033[91m%-6s\033[0m ]\n"%"FAILED" |
| 83 | + sys.stdout.write(output) |
| 84 | + sys.stdout.flush() |
| 85 | + |
| 86 | + def debug(self, msg): |
| 87 | + msg = "DEBUG:%s"%msg |
| 88 | + sys.stdout.write(msg) |
| 89 | + sys.stdout.flush() |
| 90 | + |
| 91 | + def putDbProperty(self, key, value): |
| 92 | + if self.dbDotProperties.has_key(key): |
| 93 | + (oldValue, index) = self.dbDotProperties[key] |
| 94 | + self.dbDotProperties[key] = (value, index) |
| 95 | + else: |
| 96 | + self.dbDotProperties[key] = (value, self.dbDotPropertiesIndex) |
| 97 | + self.dbDotPropertiesIndex += 1 |
| 98 | + |
| 99 | + def getDbProperty(self, key): |
| 100 | + if not self.dbDotProperties.has_key(key): |
| 101 | + return None |
| 102 | + (value, index) = self.dbDotProperties[key] |
| 103 | + return value |
| 104 | + |
| 105 | + def errorAndExit(self, msg): |
| 106 | + self.postRun() |
| 107 | + err = '''\n\nWe apologize for below error: |
| 108 | +*************************************************************** |
| 109 | +%s |
| 110 | +*************************************************************** |
| 111 | +Please run: |
| 112 | +
|
| 113 | + cloud-setup-encryption -h |
| 114 | +
|
| 115 | +for full help |
| 116 | +''' % msg |
| 117 | + sys.stderr.write(err) |
| 118 | + sys.stderr.flush() |
| 119 | + sys.exit(1) |
| 120 | + |
| 121 | + def prepareDBFiles(self): |
| 122 | + def prepareDBDotProperties(): |
| 123 | + dbpPath = os.path.join(self.dbConfPath, 'db.properties') |
| 124 | + dbproperties = file(dbpPath).read().splitlines() |
| 125 | + newdbp = [] |
| 126 | + emptyLine = 0 |
| 127 | + for line in dbproperties: |
| 128 | + passed = False |
| 129 | + line = line.strip() |
| 130 | + if line.startswith("#"): key = line; value = ''; passed = True |
| 131 | + if line == '' or line == '\n': key = self.magicString + str(emptyLine); value = ''; emptyLine += 1; passed = True |
| 132 | + |
| 133 | + try: |
| 134 | + if not passed: |
| 135 | + (key, value) = line.split('=', 1) |
| 136 | + except Exception, e: |
| 137 | + err = '''Wrong format in %s (%s): |
| 138 | +Besides comments beginning "#" and empty line, all key-value pairs must be in formula of |
| 139 | + key=value |
| 140 | +for example: |
| 141 | + db.cloud.username = cloud |
| 142 | + ''' % (dbpPath, line) |
| 143 | + self.errorAndExit(err) |
| 144 | + self.putDbProperty(key, value) |
| 145 | + self.info("Preparing %s"%dbpPath, True) |
| 146 | + |
| 147 | + prepareDBDotProperties() |
| 148 | + |
| 149 | + def finalize(self): |
| 150 | + def finalizeDbProperties(): |
| 151 | + entries = [] |
| 152 | + for key in self.dbDotProperties.keys(): |
| 153 | + (value, index) = self.dbDotProperties[key] |
| 154 | + if key.startswith("#"): |
| 155 | + entries.insert(index, key) |
| 156 | + elif key.startswith(self.magicString): |
| 157 | + entries.insert(index, '') |
| 158 | + else: |
| 159 | + entries.insert(index, "%s=%s"%(key, value)) |
| 160 | + file(os.path.join(self.dbConfPath, 'db.properties'), 'w').write('\n'.join(entries)) |
| 161 | + |
| 162 | + self.info("Finalizing setup ...", None) |
| 163 | + finalizeDbProperties() |
| 164 | + self.info(None, True) |
| 165 | + self.success = True # At here, we have done successfully and nothing more after this flag is set |
| 166 | + |
| 167 | + def processEncryptionStuff(self): |
| 168 | + def encrypt(input): |
| 169 | + cmd = ['java','-classpath',self.encryptionJarPath,'org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI', 'encrypt.sh', 'input=%s'%input, 'password=%s'%self.mgmtsecretkey,'verbose=false'] |
| 170 | + return runCmd(cmd).strip('\n') |
| 171 | + |
| 172 | + def saveMgmtServerSecretKey(): |
| 173 | + if self.encryptiontype == 'file': |
| 174 | + file(self.encryptionKeyFile, 'w').write(self.mgmtsecretkey) |
| 175 | + |
| 176 | + def formatEncryptResult(value): |
| 177 | + return 'ENC(%s)'%value |
| 178 | + |
| 179 | + def encryptDBSecretKey(): |
| 180 | + self.putDbProperty('db.cloud.encrypt.secret', formatEncryptResult(encrypt(self.dbsecretkey))) |
| 181 | + |
| 182 | + def encryptDBPassword(): |
| 183 | + dbPassword = self.getDbProperty('db.cloud.password') |
| 184 | + if dbPassword == '': return # Don't encrypt empty password |
| 185 | + if dbPassword == None: self.errorAndExit('Cannot find db.cloud.password in %s'%os.path.join(self.dbConfPath, 'db.properties')) |
| 186 | + self.putDbProperty('db.cloud.password', formatEncryptResult(encrypt(dbPassword))) |
| 187 | + |
| 188 | + usagePassword = self.getDbProperty('db.usage.password') |
| 189 | + if usagePassword == '': return # Don't encrypt empty password |
| 190 | + if usagePassword == None: self.errorAndExit('Cannot find db.usage.password in %s'%os.path.join(self.dbConfPath, 'db.properties')) |
| 191 | + self.putDbProperty('db.usage.password', formatEncryptResult(encrypt(usagePassword))) |
| 192 | + |
| 193 | + self.info("Processing encryption ...", None) |
| 194 | + self.putDbProperty("db.cloud.encryption.type", self.encryptiontype) |
| 195 | + saveMgmtServerSecretKey() |
| 196 | + encryptDBSecretKey() |
| 197 | + encryptDBPassword() |
| 198 | + self.info(None, True) |
| 199 | + |
| 200 | + def parseOptions(self): |
| 201 | + def parseOtherOptions(): |
| 202 | + self.encryptiontype = self.options.encryptiontype |
| 203 | + self.mgmtsecretkey = self.options.mgmtsecretkey |
| 204 | + self.dbsecretkey = self.options.dbsecretkey |
| 205 | + self.isDebug = self.options.debug |
| 206 | + |
| 207 | + |
| 208 | + def validateParameters(): |
| 209 | + if self.encryptiontype != 'file' and self.encryptiontype != 'web': |
| 210 | + self.errorAndExit('Wrong encryption type %s, --encrypt-type can only be "file" or "web'%self.encryptiontype) |
| 211 | + |
| 212 | + #---------------------- option parsing and command line checks ------------------------ |
| 213 | + usage = """%prog [-e ENCRYPTIONTYPE] [-m MGMTSECRETKEY] [-k DBSECRETKEY] [--debug] |
| 214 | + |
| 215 | + This command sets up the CloudStack Encryption. |
| 216 | + |
| 217 | + """ |
| 218 | + self.parser = OptionParser(usage=usage) |
| 219 | + self.parser.add_option("-v", "--debug", action="store_true", dest="debug", default=False, |
| 220 | + help="If enabled, print the commands it will run as they run") |
| 221 | + self.parser.add_option("-e", "--encrypt-type", action="store", type="string", dest="encryptiontype", default="file", |
| 222 | + help="Encryption method used for db password encryption. Valid values are file, web. Default is file.") |
| 223 | + self.parser.add_option("-m", "--managementserver-secretkey", action="store", type="string", dest="mgmtsecretkey", default="password", |
| 224 | + help="Secret key used to encrypt confidential parameters in db.properties. A string, default is password") |
| 225 | + self.parser.add_option("-k", "--database-secretkey", action="store", type="string", dest="dbsecretkey", default="password", |
| 226 | + help="Secret key used to encrypt sensitive database values. A string, default is password") |
| 227 | + |
| 228 | + (self.options, self.args) = self.parser.parse_args() |
| 229 | + parseOtherOptions() |
| 230 | + validateParameters() |
| 231 | + |
| 232 | + def run(self): |
| 233 | + try: |
| 234 | + self.preRun() |
| 235 | + self.parseOptions() |
| 236 | + self.prepareDBFiles() |
| 237 | + self.processEncryptionStuff() |
| 238 | + self.finalize() |
| 239 | + finally: |
| 240 | + self.postRun() |
| 241 | + |
| 242 | + print '' |
| 243 | + print "CloudStack has successfully setup Encryption" |
| 244 | + print '' |
| 245 | + |
| 246 | +if __name__ == "__main__": |
| 247 | + o = DBDeployer() |
| 248 | + o.run() |
| 249 | + |
0 commit comments