blob: cd9212a119b6f201ddf64f30313cb07e5d0542a9 [file] [log] [blame]
#!/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/jasypt-1.9.3.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(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()