Bug 11990 - Add encryption properties to db.properties during install

status 11990: resolved fixed
This commit is contained in:
frank 2011-11-23 16:54:20 -08:00
parent f14e536cea
commit a23e8af810

View File

@ -10,6 +10,7 @@ import string
from optparse import OptionParser from optparse import OptionParser
import commands import commands
import MySQLdb import MySQLdb
import shutil
# squelch mysqldb spurious warnings # squelch mysqldb spurious warnings
import warnings import warnings
@ -42,8 +43,38 @@ class DBDeployer(object):
ip = None ip = None
user,password,host,port,rootuser,rootpassword = [None,None,None,None,None,None] user,password,host,port,rootuser,rootpassword = [None,None,None,None,None,None]
isDebug = False isDebug = False
mgmtsecretkey = None
dbsecretkey = None
encryptiontype = None
dbConfPath = r"@MSCONF@" dbConfPath = r"@MSCONF@"
dbFilesPath = r"@SETUPDATADIR@" dbFilesPath = r"@SETUPDATADIR@"
dbDotProperties = {}
dbDotPropertiesIndex = 0
encryptionKeyFile = '@MSCONF@/key'
encryptionJarPath = '@JAVADIR@/cloud-jasypt-1.8.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 not self.success:
shutil.copy2(copyPath, dbpPath)
os.remove(copyPath)
cleanOrRecoverDbDotProperties()
def info(self, msg, result=None): def info(self, msg, result=None):
output = "" output = ""
@ -62,14 +93,28 @@ class DBDeployer(object):
sys.stdout.write(msg) sys.stdout.write(msg)
sys.stdout.flush() 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 runMysql(self, text, table, isRoot=False): def runMysql(self, text, table, isRoot=False):
kwargs = {} kwargs = {}
if not isRoot: if not isRoot:
kwargs['user'] = self.user kwargs['user'] = self.user
if self.password: kwargs['passwd'] = self.password if self.password != '': kwargs['passwd'] = self.password
else: else:
kwargs['user'] = self.rootuser kwargs['user'] = self.rootuser
if self.password: kwargs['passwd'] = self.rootpassword if self.rootpassword != '': kwargs['passwd'] = self.rootpassword
kwargs['port'] = self.port kwargs['port'] = self.port
kwargs['host'] = self.host kwargs['host'] = self.host
@ -133,6 +178,7 @@ Sql parameters:
self.errorAndExit(err) self.errorAndExit(err)
def errorAndExit(self, msg): def errorAndExit(self, msg):
self.postRun()
if self.parser != None: if self.parser != None:
self.parser.error(msg) self.parser.error(msg)
else: else:
@ -223,22 +269,56 @@ Sql parameters:
dbpPath = os.path.join(self.dbConfPath, 'db.properties') dbpPath = os.path.join(self.dbConfPath, 'db.properties')
dbproperties = file(dbpPath).read().splitlines() dbproperties = file(dbpPath).read().splitlines()
newdbp = [] newdbp = []
emptyLine = 0
for line in dbproperties: for line in dbproperties:
if line.startswith("cluster.node.IP="): line="cluster.node.IP=%s"%self.ip passed = False
if line.startswith("db.cloud.username="): line="db.cloud.username=%s"%self.user line = line.strip()
if line.startswith("db.cloud.password="): line="db.cloud.password=%s"%self.password if line.startswith("#"): key = line; value = ''; passed = True
if line.startswith("db.cloud.host="): line="db.cloud.host=%s"%self.host if line == '' or line == '\n': key = self.magicString + str(emptyLine); value = ''; emptyLine += 1; passed = True
if line.startswith("db.cloud.port="): line="db.cloud.port=%s"%self.port
if line.startswith("db.usage.username="): line="db.usage.username=%s"%self.user try:
if line.startswith("db.usage.password="): line="db.usage.password=%s"%self.password if not passed:
if line.startswith("db.usage.host="): line="db.usage.host=%s"%self.host (key, value) = line.split('=', 1)
if line.startswith("db.usage.port="): line="db.usage.port=%s"%self.port if key == "cluster.node.IP": value = self.ip
newdbp.append(line) if key == "db.cloud.username": value = self.user
file(dbpPath,"w").write("\n".join(newdbp)) if key == "db.cloud.password": value = self.password
if key == "db.cloud.host": value = self.host
if key == "db.cloud.port": value = self.port
if key == "db.usage.username": value = self.user
if key == "db.usage.password": value = self.password
if key == "db.usage.host": value = self.host
if key == "db.usage.port": value = self.port
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) self.info("Preparing %s"%dbpPath, True)
prepareDBDotProperties() 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 grabSystemInfo(self): def grabSystemInfo(self):
def getIpAddr(): def getIpAddr():
try: try:
@ -268,10 +348,11 @@ Sql parameters:
if not self.serversetup: if not self.serversetup:
coreSchemas.append('server-setup.sql') coreSchemas.append('server-setup.sql')
coreSchemas = [os.path.join(self.dbFilesPath, x) for x in coreSchemas] checkingList = [os.path.join(self.dbFilesPath, x) for x in coreSchemas]
for f in coreSchemas: checkingList.append(self.encryptionJarPath)
for f in checkingList:
if not os.path.isfile(f): if not os.path.isfile(f):
self.errorAndExit("Cloud DB schema %s was not found"%f) self.errorAndExit("Cloud DB required file %s was not found"%f)
self.info(None, True) self.info(None, True)
def checkDbserverHostname(): def checkDbserverHostname():
@ -312,6 +393,33 @@ Sql parameters:
checkSELinux() checkSELinux()
checkMysqlConnection() checkMysqlConnection()
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 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 == 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 == 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)
file(self.encryptionKeyFile, 'w').write(self.encryptiontype)
self.putDbProperty("db.cloud.encryption.type", self.encryptiontype)
encryptDBSecretKey()
encryptDBPassword()
self.info(None, True)
def parseOptions(self): def parseOptions(self):
def parseOtherOptions(): def parseOtherOptions():
if self.options.rootcreds: if self.options.rootcreds:
@ -330,6 +438,9 @@ Sql parameters:
self.serversetup = self.options.serversetup self.serversetup = self.options.serversetup
self.info("User specified server-setup.sql file at %s"%self.serversetup, True) self.info("User specified server-setup.sql file at %s"%self.serversetup, True)
self.encryptiontype = self.options.encryptiontype
self.mgmtsecretkey = self.options.mgmtsecretkey
self.dbsecretkey = self.options.dbsecretkey
self.isDebug = self.options.debug self.isDebug = self.options.debug
def parseUserAndPassword(cred): def parseUserAndPassword(cred):
@ -340,14 +451,14 @@ Sql parameters:
if len(user) < 1: if len(user) < 1:
self.errorAndExit("Invalid user name and password format, must be in format of user:password, user name can not be empty") self.errorAndExit("Invalid user name and password format, must be in format of user:password, user name can not be empty")
if len(stuff) == 1: if len(stuff) == 1:
password = None password = ''
else: else:
password = stuff[1] password = stuff[1]
forbidden = "' \\`" forbidden = "' \\`"
for f in forbidden: for f in forbidden:
if f in user: self.errorAndExit("User name cannot have the %r characters"%f) if f in user: self.errorAndExit("User name cannot have the %r characters"%f)
if password and f in password: self.errorAndExit("Password cannot have the %r characters"%f) if f in password: self.errorAndExit("Password cannot have the %r characters"%f)
return user, password return user, password
def parseCasualCredit(): def parseCasualCredit():
@ -371,7 +482,7 @@ Sql parameters:
self.errorAndExit("There are more than one parameters for user:password@hostname (%s)"%self.args) self.errorAndExit("There are more than one parameters for user:password@hostname (%s)"%self.args)
arg = self.args[0] arg = self.args[0]
stuff = arg.split("@", 2) stuff = arg.split("@", 1)
if len(stuff) == 1: stuff.append("localhost") if len(stuff) == 1: stuff.append("localhost")
self.user,self.password = parseUserAndPassword(stuff[0]) self.user,self.password = parseUserAndPassword(stuff[0])
self.host,self.port = parseHostInfo(stuff[1]) self.host,self.port = parseHostInfo(stuff[1])
@ -423,17 +534,32 @@ Sql parameters:
help="Colon-separated user name and password of a MySQL user with administrative privileges") help="Colon-separated user name and password of a MySQL user with administrative privileges")
self.parser.add_option("-a", "--auto", action="store", type="string", dest="serversetup", default="", self.parser.add_option("-a", "--auto", action="store", type="string", dest="serversetup", default="",
help="Path to an XML file describing an automated unattended cloud setup") help="Path to an XML file describing an automated unattended cloud setup")
self.parser.add_option("-e", "--encrypt-type", action="store", type="string", dest="encryptiontype", default="file",
help="Encryption method used for db password encryption, could be file. Default is file")
self.parser.add_option("-m", "--managementserver-secretkey", action="store", type="string", dest="mgmtsecretkey", default="password",
help="Secret key used for encrypt. A string, default is password")
self.parser.add_option("-k", "--database-secretkey", action="store", type="string", dest="dbsecretkey", default="password",
help="Secret key used for encrypt. A string, default is password")
self.parser.add_option("-t", "--test-encryption", action="store_true", dest="testencryption", default=False,
help="If enabled, will process encryption")
(self.options, self.args) = self.parser.parse_args() (self.options, self.args) = self.parser.parse_args()
parseCasualCredit() parseCasualCredit()
parseOtherOptions() parseOtherOptions()
def run(self): def run(self):
try:
self.preRun()
self.parseOptions() self.parseOptions()
self.checkSystemSetup() self.checkSystemSetup()
self.grabSystemInfo() self.grabSystemInfo()
self.prepareDBFiles() self.prepareDBFiles()
self.setupDBSchema() self.setupDBSchema()
if self.options.testencryption:
self.processEncryptionStuff()
self.finalize()
finally:
self.postRun()
print '' print ''
print "CloudStack has successfully initialized database, you can check your database configuration in %s"%os.path.join(self.dbConfPath, 'db.properties') print "CloudStack has successfully initialized database, you can check your database configuration in %s"%os.path.join(self.dbConfPath, 'db.properties')