cloudstack/setup/bindir/cloud-setup-encryption.in

345 lines
11 KiB
Python
Executable File

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