mirror of
https://github.com/clearlinux/WALinuxAgent.git
synced 2026-09-06 13:51:29 +00:00
Add provision handler
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user