Add provision handler

This commit is contained in:
Yue Zhang
2014-11-07 17:43:48 +08:00
parent fdb4bbe9d7
commit c0159cda73
7 changed files with 277 additions and 49 deletions
+14
View File
@@ -20,7 +20,9 @@
import env
import test.tools as tools
from tools import *
import uuid
import shutil
import unittest
import os
import walinuxagent.logger as logger
@@ -51,6 +53,8 @@ class TestUserOperation(unittest.TestCase):
userName))
self.assertTrue(os.path.isdir(os.path.join(CurrOS.GetHome(), userName)))
MockSshdConfigPath=MockFunc("GetSshdConfigPath", "/tmp/sshd_config")
class TestSshOperation(unittest.TestCase):
def _setUp(self):
logger.AddLoggerAppender(logger.AppenderConfig({
@@ -59,6 +63,16 @@ class TestSshOperation(unittest.TestCase):
"console_path":"/dev/stdout"
}))
@Mockup(CurrOS, "GetSshdConfigPath", MockSshdConfigPath)
def test_config_sshd(self):
shutil.copyfile(os.path.join(env.test_root, "sshd_config"),
CurrOS.GetSshdConfigPath())
CurrOS.ConfigSshd(True)
simple_file_grep(CurrOS.GetSshdConfigPath(),
"PasswordAuthentication no")
simple_file_grep(CurrOS.GetSshdConfigPath(),
"ChallengeResponseAuthentication no")
def test_regen_ssh_host_key(self):
oldKey = fileutil.GetFileContents('/etc/ssh/ssh_host_rsa_key')
CurrOS.RegenerateSshHostkey('rsa')
+90
View File
@@ -0,0 +1,90 @@
# Package generated configuration file
# See the sshd_config(5) manpage for details
# What ports, IPs and protocols we listen for
Port 22
# Use these options to restrict which interfaces/protocols sshd will bind to
#ListenAddress ::
#ListenAddress 0.0.0.0
Protocol 2
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
#Privilege Separation is turned on for security
UsePrivilegeSeparation yes
# Lifetime and size of ephemeral version 1 server key
KeyRegenerationInterval 3600
ServerKeyBits 1024
# Logging
SyslogFacility AUTH
LogLevel INFO
# Authentication:
LoginGraceTime 120
PermitRootLogin without-password
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
#AuthorizedKeysFile %h/.ssh/authorized_keys
# Don't read the user's ~/.rhosts and ~/.shosts files
IgnoreRhosts yes
# For this to work you will also need host keys in /etc/ssh_known_hosts
RhostsRSAAuthentication no
# similar for protocol version 2
HostbasedAuthentication no
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
#IgnoreUserKnownHosts yes
# To enable empty passwords, change to yes (NOT RECOMMENDED)
PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Change to no to disable tunnelled clear text passwords
#PasswordAuthentication yes
# Kerberos options
#KerberosAuthentication no
#KerberosGetAFSToken no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
X11Forwarding yes
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
#UseLogin no
#MaxStartups 10:30:60
#Banner /etc/issue.net
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
Match group root
+3
View File
@@ -140,6 +140,9 @@ class OvfEnv(object):
def getUserPassword(self):
return self.UserPassword
def clearUserPassword(self):
self.UserPassword = None
def getCustomData(self):
return self.CustomData
+3 -22
View File
@@ -549,15 +549,15 @@ class Certificates(object):
beginCrt = True
elif re.match(r'[-]+END.*KEY[-]+', line):
tmpFile = self.writeToTempFile(index, 'prv', buf)
pub = self.getPubKeyFromPrv(tmpFile)
pub = CurrOS.GetPubKeyFromPrv(tmpFile)
prvs[pub] = tmpFile
buf = []
index += 1
beginPrv = False
elif re.match(r'[-]+END.*CERTIFICATE[-]+', line):
tmpFile = self.writeToTempFile(index, 'crt', buf)
pub = self.getPubKeyFromCrt(tmpFile)
thumbprint = self.getThumbprintFromCrt(tmpFile)
pub = CurrOS.GetPubKeyFromCrt(tmpFile)
thumbprint = CurrOS.GetThumbprintFromCrt(tmpFile)
thumbprints[pub] = thumbprint
#Rename crt with thumbprint as the file name
crt = "{0}.crt".format(thumbprint)
@@ -596,25 +596,6 @@ class Certificates(object):
tmp.writelines(buf)
return fileName
def getPubKeyFromPrv(self, fileName):
cmd = "{0} rsa -in {1} -pubout 2>/dev/null".format(self.opensslCmd,
fileName)
pub = shellutil.RunGetOutput(cmd)[1]
return pub
def getPubKeyFromCrt(self, fileName):
cmd = "{0} x509 -in {1} -pubkey -noout".format(self.opensslCmd,
fileName)
pub = shellutil.RunGetOutput(cmd)[1]
return pub
def getThumbprintFromCrt(self, fileName):
cmd = "{0} x509 -in {1} -fingerprint -noout".format(self.opensslCmd,
fileName)
thumbprint = shellutil.RunGetOutput(cmd)[1]
thumbprint = thumbprint.rstrip().split('=')[1].replace(':', '').upper()
return thumbprint
class ExtensionsConfig(object):
"""
parse ExtensionsConfig, downloading and unpacking them to /var/lib/waagent.
+26 -18
View File
@@ -21,46 +21,57 @@ import os
import walinuxagent.logger as logger
from walinuxagent.utils.osutil import CurrOS, CurrOSInfo
import walinuxagent.utils.shellutil as shellutil
from CurrOS import LibDir, OvfMountPoint
CustomDataFile="CustomData"
class ProvisionHandler(object):
def __init__(self, config, protocol, libDir=LibDir):
def __init__(self, config, protocol):
self.config = config
self.protocol = protocol
self.libDir = libDir
def provision(self):
if self.config.getSwitch("Provisioning.Enabled"):
return
logger.Info("Provisioning image started")
ovfenv = self.protocol.copyOvf()
self.setHostName(ovfenv.getComputerName())
self.createUserAccount(ovfenv.getUserName(), ovfenv.getUserPassword())
self.deploySshPublicKeys(ovfenv.getSshPublicKeys)
self.saveCustomData(ovfenv.getCustomData())
self.ovfenv = self.protocol.copyOvf()
password = self.ovfenv.getUserPassword()
self.ovfenv.clearUserPassword()
self.setHostName()
self.createUserAccount(self.ovfenv.getUserName(), password)
self.deploySshPublicKeys()
self.deploySshKeyPairs()
self.saveCustomData()
if config.getSwitch("Provisioning.RegenerateSshHostKeyPair"):
keyPairType = config.get("Provisioning.SshHostKeyPairType", "rsa")
CurrOS.RegenerateSshHostkey(keyPairType)
#TODO Wait for host name published
CurrOS.RestartSshService()
self.reportSshHostkeyThumbnail()
#TODO report provision status
self.protocol.reportProvisionStatus("")
if config.getSwitch("Provisioning.DeleteRootPassword"):
self.deleteRootPassword()
def saveCustomData(self, customData):
CurrOS.SetFileContents(os.path.join(self.libDir, CustomDataFile),
libDir = CurrOS.GetLibDir()
CurrOS.SetFileContents(os.path.join(libDir, CustomDataFile),
customData)
def deploySshPublicKeys(self, keys):
pass
def deploySshPublicKeys(self):
for thumbprint, path in self.ovfenv.getSshPublicKeys():
CurrOS.DeploySshPublicKey(self.ovfenv.getUserName(), thumbprint, path)
def setHostName(self, hostName):
def deploySshKeyPairs(self):
for thumbprint, path in self.ovfenv.getSshKeyPairs():
CurrOS.DeploySshKeyPair(self.ovfenv.getUserName(), thumbprint, path)
def setHostName(self):
pass
def createUserAccount(self, userName, password):
@@ -68,10 +79,7 @@ class ProvisionHandler(object):
raise Exception("User name is empty.")
if CurrOS.IsSysUser(userName):
raise Exception("User:{0} is a system user.".format(userName))
CurrOS.CreateUserAccount(userName, password)
def reportSshHostkeyThumbnail(self):
pass
CurrOS.UpdateUserAccount(userName, password)
def deleteRootPassword(self):
CurrOS.DeleteRootPassword()
+1 -1
View File
@@ -120,7 +120,7 @@ def GetLineStartingWith(prefix, filepath):
#End File operation util functions
def CreateDir(dirpath, owner, mode):
if os.path.isdir(dirpath):
if not os.path.isdir(dirpath):
os.makedirs(dirpath, mode)
ChangeOwner(dirpath, owner)
+140 -8
View File
@@ -103,7 +103,7 @@ class DefaultDistro():
def ChangePassword(self, userName, password):
shellutil.RunSendStdin("chpasswd",
"{0}:{1}\n".format(userName, password))
def ConfigSudoer(self, userName, nopasswd):
# for older distros create sudoers.d
if not os.path.isdir('/etc/sudoers.d/'):
@@ -120,21 +120,153 @@ class DefaultDistro():
sudoer = "{0} ALL = (ALL) ALL\n".format(userName)
fileutil.SetFileContents('/etc/sudoers.d/waagent', sudoer)
os.chmod('/etc/sudoers.d/waagent', 0440)
def DeleteRootPassword(self):
pass
def GetHome(self):
return '/home'
def ConfigSshKey(self, userName, thumbprint):
sshDir = os.path.join(self.getHome(), userName, '.ssh')
fileutil.CreateDir(sshDir, userName, '0700')
pub = os.path.join(sshDir, 'id_rsa.pub')
prv = os.path.join(sshDir, 'id_rsa')
def GetPubKeyFromPrv(self, fileName):
cmd = "{0} rsa -in {1} -pubout 2>/dev/null".format(self.GetOpensslCmd(),
fileName)
pub = shellutil.RunGetOutput(cmd)[1]
return pub
def GetPubKeyFromCrt(self, fileName):
cmd = "{0} x509 -in {1} -pubkey -noout".format(self.GetOpensslCmd(),
fileName)
pub = shellutil.RunGetOutput(cmd)[1]
return pub
def NormPath(self, filepath):
home = CurrOS.GetHome()
# Expand HOME variable if present in path
path = os.path.normpath(filepath.replace("$HOME", home))
def GetThumbprintFromCrt(self, fileName):
cmd="{0} x509 -in {1} -fingerprint -noout".format(self.GetOpensslCmd(),
fileName)
thumbprint = shellutil.RunGetOutput(cmd)[1]
thumbprint = thumbprint.rstrip().split('=')[1].replace(':', '').upper()
return thumbprint
def DeploySshKeyPair(self, userName, thumbprint, path):
"""
Deploy id_rsa and id_rsa.pub
"""
path = self.NormPath(path)
dirPath = os.path.dirname(path)
fileutil.CreateDir(dirPath, userName, 0700)
libDir = CurrOS.GetLibDir()
prvPath = os.path.join(libDir, thumbprint + '.prv')
if not os.path.isfile(prvPath):
logger.Error("Failed to deploy key pair, thumbprint: {0}",
thumbprint)
return
shutil.copyfile(prvPath, path)
pubPath = path + '.pub'
pub = self.GetPubKeyFromPrv(prvPath)
fileutil.SetFileContents(pubPath, pub)
self.SetSelinuxContext(path, 'unconfined_u:object_r:ssh_home_t:s')
self.SetSelinuxContext(pubPath, 'unconfined_u:object_r:ssh_home_t:s')
os.chmod(path, 0600)
os.chmod(pubPath, 0600)
def DeploySshPublicKey(self, userName, thumbprint, path):
"""
Deploy authorized_key
"""
path = self.NormPath(path)
dirPath = os.path.dirname(path)
fileutil.CreateDir(dirPath, userName, 0700)
libDir = CurrOS.GetLibDir()
crtPath = os.path.join(libDir, thumbprint + '.crt')
if not os.path.isfile(crtPath):
logger.Error("Failed to deploy public key, thumbprint: {0}",
thumbprint)
return
pubPath = os.path.join(libDir, thumbprint + '.pub')
pub = self.GetPubKeyFromCrt(crtPath)
fileutil.SetFileContents(pubPath, pub)
self.SetSelinuxContext(pubPath, 'unconfined_u:object_r:ssh_home_t:s')
shellutil.Run("ssh-keygen -i -m PKCS8 -f {0} >> {1}", thumbprint, path)
self.SetSelinuxContext(path, 'unconfined_u:object_r:ssh_home_t:s')
os.chmod(path, 0600)
os.chmod(pubPath, 0600)
def IsSelinuxSystem(self):
"""
Checks and sets self.selinux = True if SELinux is available on system.
"""
if self.selinux == None:
if shellutil.Run("which getenforce", chk_err=False):
self.selinux = False
else:
self.selinux = True
return self.selinux
def IsSelinuxRunning(self):
"""
Calls shell command 'getenforce' and returns True if 'Enforcing'.
"""
if self.IsSelinuxSystem():
output = shellutil.RunGetOutput("getenforce")[1]
return output.startswith("Enforcing")
else:
return False
def SetSelinuxEnforce(self, state):
"""
Calls shell command 'setenforce' with 'state'
and returns resulting exit code.
"""
if self.IsSelinuxSystem():
if state: s = '1'
else: s='0'
return shellutil.Run("setenforce "+s)
def SetSelinuxContext(self, path, cn):
"""
Calls shell 'chcon' with 'path' and 'cn' context.
Returns exit result.
"""
if self.IsSelinuxSystem():
return shellutil.Run('chcon ' + cn + ' ' + path)
def GetSshdConfigPath(self):
return "/etc/ssh/sshd_config"
def ConfigSshd(self, disablePassword):
if not disablePassword:
return
configPath = self.GetSshdConfigPath()
config = fileutil.GetFileContents(configPath).split("\n")
passwordAuthFound = False
challengeAuthFound = False
for i in range(0, len(config)):
if config[i].startswith("PasswordAuthentication"):
passwordAuthFound = True
config[i] = "PasswordAuthentication no"
elif config[i].startswith("ChallengeResponseAuthentication"):
challengeAuthFound = True
config[i] = "ChallengeResponseAuthentication no"
elif config[i].startswith("Match"):
#Match block must be put in the end of sshd config
break
if not passwordAuthFound:
config.insert(i, "PasswordAuthentication no")
if not challengeAuthFound:
config.insert(i, "ChallengeResponseAuthentication no")
logger.Info("Disabled SSH password-based authentication methods.")
fileutil.ReplaceFileContentsAtomic(configPath, "\n".join(config))
def RegenerateSshHostkey(self, keyPairType):
shellutil.Run("rm -f /etc/ssh/ssh_host_*key*")
shellutil.Run("ssh-keygen -N '' -t {0} -f /etc/ssh/ssh_host_{1}_key"
.format(keyPairType, keyPairType))
self.RestartSshService()
def GetOpensslCmd(self):
return '/usr/bin/openssl'