mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-11-04 00:02:37 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			345 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			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()
 |