From ed2e03ea825603c0a0622a1eb9e23f19e4a4a315 Mon Sep 17 00:00:00 2001 From: xue_meng_en <1836611252@qq.com> Date: Thu, 1 Dec 2022 11:42:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9B=E5=BB=BAssl=E6=89=80=E9=9C=80?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tool/cm_tool/Common.py | 4 +- tool/cm_tool/CreateCMCACert.sh | 82 +++++++++++++++++++ tool/cm_tool/InstallImpl.py | 141 +++++++++++++++++++++++++++++++-- tool/cm_tool/cm_install | 4 +- tool/cm_tool/cm_uninstall | 4 +- 5 files changed, 224 insertions(+), 11 deletions(-) create mode 100755 tool/cm_tool/CreateCMCACert.sh diff --git a/tool/cm_tool/Common.py b/tool/cm_tool/Common.py index b8ee2dd..ad1751a 100755 --- a/tool/cm_tool/Common.py +++ b/tool/cm_tool/Common.py @@ -61,14 +61,12 @@ def checkXMLFile(xmlFile): if not os.access(xmlFile, os.R_OK): CMLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50100"] % (xmlFile, "current user")) -def checkHostsTrust(hosts, localhost = ""): +def checkHostsTrust(hosts): """ check trust between current host and the given hosts """ hostsWithoutTrust = [] for host in hosts: - if host == localhost: - continue checkTrustCmd = "ssh -o ConnectTimeout=3 -o ConnectionAttempts=5 -o PasswordAuthentication=no " \ "-o StrictHostKeyChecking=no %s 'pwd > /dev/null'" % host status, output = subprocess.getstatusoutput(checkTrustCmd) diff --git a/tool/cm_tool/CreateCMCACert.sh b/tool/cm_tool/CreateCMCACert.sh new file mode 100755 index 0000000..3257f67 --- /dev/null +++ b/tool/cm_tool/CreateCMCACert.sh @@ -0,0 +1,82 @@ +#!/bin/bash +############################################################################# +# Copyright (c) 2022 Huawei Technologies Co.,Ltd. +# +# openGauss is licensed under Mulan PSL v2. +# You can use this software according to the terms +# and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, +# WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# See the Mulan PSL v2 for more details. +# ---------------------------------------------------------------------------- +# Description : CreateCMCACert.sh +############################################################################# +set -e +envfile=$1 +if [ "$envfile" = "" ]; then + echo "Envfile is needed." + exit 1 +fi + +activePeriod=$2 +if [ "$activePeriod" == "" ]; then + activePeriod=10950 +fi + +read -s passwd +source $envfile +certPath=$GAUSSHOME/share/sslcert/cm +if [ ! -f "$certPath/openssl.cnf" ]; then + echo "CM ssl conf does not exist." + exit 1 +fi +export OPENSSL_CONF=$GAUSSHOME/share/sslcert/gsql/openssl.cnf +if [ ! -f "$OPENSSL_CONF" ]; then + echo "ssl config file does not exist." + exit 1 +fi + +# generate root cert +## cakey.pem +echo "$passwd" | openssl genrsa -aes256 -f4 -passout stdin -out $certPath/cakey.pem 2048 +## cacert.pem +echo "$passwd" | openssl req -new -x509 -passin stdin -days $activePeriod -key $certPath/cakey.pem -out $certPath/cacert.pem -subj "/C=CN/ST=NULL/L=NULL/O=NULL/OU=NULL/CN=CA" + +# generate server and client cert +for role in "server" "client"; +do + ## key + echo "$passwd" | openssl genrsa -aes256 -passout stdin -out $certPath/$role.key 2048 + ## csr + echo "$passwd" | openssl req -new -key $certPath/$role.key -passin stdin -out $certPath/$role.csr -subj "/C=CN/ST=NULL/L=NULL/O=NULL/OU=NULL/CN=$role" + ## crt + echo "$passwd" | openssl x509 -req -days $activePeriod -in $certPath/$role.csr -CA $certPath/cacert.pem -CAkey $certPath/cakey.pem -passin stdin -CAcreateserial -out $certPath/$role.crt -extfile $certPath/openssl.cnf +done + +# generate server cipher and rand +expect -c " + spawn cm_ctl encrypt -M server -D $certPath; + expect { + \"*password*\" { send \"$passwd\r\"; exp_continue } + } +" + +# generate client cipher and rand +expect -c " + spawn cm_ctl encrypt -M client -D $certPath; + expect { + \"*password*\" { send \"$passwd\r\"; exp_continue } + } +" +# set the password to null and unset it +passwd="" +unset passwd + +# change to readonly +chmod 400 $certPath/* diff --git a/tool/cm_tool/InstallImpl.py b/tool/cm_tool/InstallImpl.py index 600e377..3578dbb 100755 --- a/tool/cm_tool/InstallImpl.py +++ b/tool/cm_tool/InstallImpl.py @@ -18,13 +18,13 @@ # Description : InstallImpl.py ############################################################################# +from curses.ascii import isdigit, islower, isupper import os import re import subprocess -import xml.etree.cElementTree as ETree +import getpass from ErrorCode import ErrorCode from Common import executeCmdOnHost -from CMLog import CMLog class InstallImpl: def __init__(self, install): @@ -139,7 +139,6 @@ class InstallImpl: cmd = """ cp {gaussHome}/share/config/cm_server.conf.sample {cmdir}/cm_server/cm_server.conf sed 's#log_dir = .*#log_dir = {gaussLog}/cm/cm_server#' {cmdir}/cm_server/cm_server.conf -i - sed 's/enable_ssl.*=.*on/enable_ssl = off/g' {cmdir}/cm_server/cm_server.conf -i """.format(gaussHome=self.gaussHome, gaussLog=self.gaussLog, cmdir=cmdir) status, output = self.executeCmdOnHost(host, cmd) if status != 0: @@ -154,7 +153,6 @@ class InstallImpl: cp {gaussHome}/share/config/cm_agent.conf.sample {cmdir}/cm_agent/cm_agent.conf && sed 's#log_dir = .*#log_dir = {gaussLog}/cm/cm_agent#' {cmdir}/cm_agent/cm_agent.conf -i && sed 's#unix_socket_directory = .*#unix_socket_directory = {gaussHome}#' {cmdir}/cm_agent/cm_agent.conf -i - sed 's/enable_ssl.*=.*on/enable_ssl = off/g' {cmdir}/cm_agent/cm_agent.conf -i """.format(gaussHome=self.gaussHome, gaussLog=self.gaussLog, cmdir=cmdir) status, output = self.executeCmdOnHost(host, cmd) if status != 0: @@ -201,7 +199,6 @@ class InstallImpl: # set crontab on other hosts setCronCmd = "crontab %s" % cronContentTmpFile cleanTmpFileCmd = "rm %s -f" % cronContentTmpFile - import getpass username = getpass.getuser() killMonitorCmd = "pkill om_monitor -u %s; " % username for host in self.hostnames: @@ -299,6 +296,139 @@ class InstallImpl: if status != 0: self.logger.logExit("Failed to refresh static file." + output) + @staticmethod + def checkPassword(passwordCA): + minPasswordLen = 8 + maxPasswordLen = 15 + kinds = [0, 0, 0, 0] + specLetters = "~!@#$%^&*()-_=+\\|[{}];:,<.>/?" + if len(passwordCA) < minPasswordLen: + print("Invalid password, it must contain at least eight characters.") + return False + if len(passwordCA) > maxPasswordLen: + print("Invalid password, it must contain at most fifteen characters.") + return False + for c in passwordCA: + if isdigit(c): + kinds[0] += 1 + elif isupper(c): + kinds[1] += 1 + elif islower(c): + kinds[2] += 1 + elif c in specLetters: + kinds[3] += 1 + else: + print("The password contains illegal character: %s." % c) + return False + kindsNum = 0 + for k in kinds: + if k > 0: + kindsNum += 1 + if kindsNum < 3: + print("The password must contain at least three kinds of characters.") + return False + return True + + def _getPassword(self): + passwordCA = "" + passwordCA2 = "" + tryCount = 0 + while tryCount < 3: + passwordCA = getpass.getpass("Please input the password for ca cert:") + passwordCA2 = getpass.getpass("Please input the password for ca cert again:") + if passwordCA != passwordCA2: + tryCount += 1 + self.logger.printMessage("The password enterd twice do not match.") + continue + if not InstallImpl.checkPassword(passwordCA): + tryCount += 1 + continue + break + if tryCount == 3: + self.logger.logExit("Maximum number of attempts has been reached.") + return passwordCA + + def _createCMSslConf(self, certPath): + """ + Generate config file. + """ + self.logger.debug("OPENSSL: Create config file.") + v3CaL = [ + "[ v3_ca ]", + "subjectKeyIdentifier=hash", + "authorityKeyIdentifier=keyid:always,issuer:always", + "basicConstraints = CA:true", + "keyUsage = keyCertSign,cRLSign", + ] + v3Ca = os.linesep.join(v3CaL) + + # Create config file. + with open(os.path.join(certPath, "openssl.cnf"), "w") as fp: + # Write config item of Signature + fp.write(v3Ca) + self.logger.debug("OPENSSL: Successfully create config file.") + + def _cleanUselessFile(self): + """ + Clean useless files + :return: NA + """ + certPath = os.path.join(self.gaussHome, "share/sslcert/cm") + keyFiles = ["cacert.pem", "server.crt", "server.key", "client.crt", "client.key", + "server.key.cipher", "server.key.rand", "client.key.cipher", "client.key.rand"] + for fileName in os.listdir(certPath): + filePath = os.path.join(certPath, fileName) + if fileName not in keyFiles: + os.remove(filePath) + + def _createCMCALocal(self): + self.logger.debug("Creating Cm ca files locally.") + certPath = os.path.join(self.gaussHome, "share/sslcert/cm") + mkdirCmd = "rm %s -rf; mkdir %s" % (certPath, certPath) + status, output = subprocess.getstatusoutput(mkdirCmd) + if status != 0: + self.logger.debug("Command: %s\nStatus: %sOutput: %s" % (mkdirCmd, status, output)) + self.logger.logExit("Failed to create cert path.") + self._createCMSslConf(certPath) + curPath = os.path.split(os.path.realpath(__file__))[0] + createCMCACert = os.path.realpath(os.path.join(curPath, "CreateCMCACert.sh")) + passwd = self._getPassword() + cmd = "echo \"%s\" | sh %s %s" % (passwd, createCMCACert, self.envFile) + # once used, set password to null and release it + passwd = "" + del passwd + status, output = subprocess.getstatusoutput(cmd) + cmd = "" + del cmd + if status != 0: + self.logger.logExit("Failed to create cm ca cert file.\n" + output) + self._cleanUselessFile() + + def _distributeCA(self): + self.logger.debug("Distributing CM ca files to other hosts.") + certPath = os.path.join(self.gaussHome, "share/sslcert/cm") + createCertPathCmd = "rm {certPath} -rf; mkdir {certPath}; chmod 700 {certPath}".format( + certPath=certPath) + for host in self.hostnames: + if host == self.localhostName: + continue + status, output = self.executeCmdOnHost(host, createCertPathCmd) + if status != 0: + errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" (createCertPathCmd, status, output) + self.logger.debug(errorDetail) + self.logger.logExit("Failed to create path of CA for CM on host %s." % host) + scpCmd = "scp {certPath}/* {host}:{certPath}".format(certPath=certPath, host=host) + status, output = subprocess.getstatusoutput(scpCmd) + if status != 0: + errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" (scpCmd, status, output) + self.logger.debug(errorDetail) + self.logger.logExit("Failed to create CA for CM.") + + def createCMCA(self): + self.logger.log("Creating CM ca files.") + self._createCMCALocal() + self._distributeCA() + def run(self): self.logger.log("Start to install cm tool.") self.prepareCMPath() @@ -306,6 +436,7 @@ class InstallImpl: self.createManualStartFile() self.initCMServer() self.initCMAgent() + self.createCMCA() self._refreshStaticFile() self.setMonitorCrontab() self.startCluster() diff --git a/tool/cm_tool/cm_install b/tool/cm_tool/cm_install index cff24ce..31062ac 100755 --- a/tool/cm_tool/cm_install +++ b/tool/cm_tool/cm_install @@ -114,6 +114,8 @@ General options: CMLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50001"] % '-cmpkg' + ".") if not os.path.exists(self.cmpkg): CMLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50201"] % self.cmpkg) + if not os.path.isfile(self.cmpkg): + CMLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50210"] % ("cmpkg " + self.cmpkg)) if self.envFile == "": self.envFile = os.path.join(os.environ['HOME'], ".bashrc") @@ -235,7 +237,7 @@ General options: self.logger.logExit("\"cmDir\" of all nodes must be provided.") def checkHostTrust(self): - checkHostsTrust(self.hostnames, self.localhostName) + checkHostsTrust(self.hostnames) def initLogger(self): logPath = os.path.join(self.gaussLog, "cm", "cm_tool") diff --git a/tool/cm_tool/cm_uninstall b/tool/cm_tool/cm_uninstall index e5a587c..b179d64 100755 --- a/tool/cm_tool/cm_uninstall +++ b/tool/cm_tool/cm_uninstall @@ -249,7 +249,7 @@ General options: if host == self.localhostName: isLocal = True cmd = "source %s; cd $GAUSSHOME/bin; rm -f om_monitor* cm_agent* cm_server* " \ - "cm_ctl* cm_persist* *manual*start*; cd -" % self.envFile + "cm_ctl* cm_persist* *manual*start* promote_mode_cms; cd -" % self.envFile status, output = executeCmdOnHost(host, cmd, isLocal) if status != 0: errorDetail = "\nCommand: %s\nStatus: %s\nOutput: %s" % (cmd, status, output) @@ -267,7 +267,7 @@ General options: CMLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50105"]) def checkHostTrust(self): - checkHostsTrust(self.hostnames, self.localhostName) + checkHostsTrust(self.hostnames) def run(self): self.checkExeUser()