#!/usr/bin/python
#
# Windows Azure Linux Agent
#
# Copyright 2012 Microsoft Corporation
#
# Licensed 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.
#
# Requires Python 2.4+ and Openssl 1.0+
#
# Implements parts of RFC 2131, 1541, 1497 and
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
#
import array
import base64
import httplib
import os
import os.path
import platform
import pwd
import re
import shutil
import socket
import SocketServer
import struct
import subprocess
import sys
import tempfile
import textwrap
import threading
import time
import traceback
import xml.dom.minidom
import commands
GuestAgentName = "WALinuxAgent"
GuestAgentLongName = "Windows Azure Linux Agent"
GuestAgentVersion = "WALinuxAgent-1.3"
ProtocolVersion = "2011-12-31"
Config = None
LinuxDistro = "UNKNOWN"
Verbose = False
WaAgent = None
DiskActivated = False
Openssl = "openssl"
Children = []
PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"]
RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
"/etc/udev/rules.d/70-persistent-net.rules" ]
VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
LibDir = "/var/lib/waagent"
# This lets us index into a string or an array of integers transparently.
def Ord(a):
if type(a) == type("a"):
a = ord(a)
return a
def IsWindows():
return (platform.uname()[0] == "Windows")
def IsLinux():
return (platform.uname()[0] == "Linux")
def DetectLinuxDistro():
global LinuxDistro
if os.path.isfile("/etc/redhat-release"):
LinuxDistro = "RedHat"
return True
if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"):
LinuxDistro = "Ubuntu"
return True
if os.path.isfile("/etc/debian_version"):
LinuxDistro = "Debian"
return True
if os.path.isfile("/etc/SuSE-release"):
LinuxDistro = "Suse"
return True
return False
def IsRedHat():
return "RedHat" in LinuxDistro
def IsUbuntu():
return "Ubuntu" in LinuxDistro
def IsDebian():
return IsUbuntu() or "Debian" in LinuxDistro
def IsSuse():
return "Suse" in LinuxDistro
def UsesRpm():
return IsRedHat() or IsSuse()
def UsesDpkg():
return IsDebian()
def GetLastPathElement(path):
return path.rsplit('/', 1)[1]
def GetFileContents(filepath):
file = None
try:
file = open(filepath)
except:
return None
if file == None:
return None
try:
return file.read()
finally:
file.close()
def SetFileContents(filepath, contents):
file = open(filepath, "w")
try:
file.write(contents)
finally:
file.close()
def AppendFileContents(filepath, contents):
file = open(filepath, "a")
try:
file.write(contents)
finally:
file.close()
def ReplaceFileContentsAtomic(filepath, contents):
handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
try:
os.write(handle, contents)
finally:
os.close(handle)
try:
os.rename(temp, filepath)
return
except:
pass
os.remove(filepath)
os.rename(temp, filepath)
def GetLineStartingWith(prefix, filepath):
for line in GetFileContents(filepath).split('\n'):
if line.startswith(prefix):
return line
return None
def Run(a):
LogIfVerbose(a)
return os.system(a)
def RunSafe(cmd):
Log(cmd)
# for python2.1 double try, in order to use a finally...
try:
try:
(exit_status,output) = commands.getstatusoutput(cmd)
except OSError,e : # just catch the exception and proceed
Log( ("OSError " + str(e) + " caught") )
return exit_status,output
else:
return exit_status,output
finally:
pass
def GetNodeTextData(a):
for b in a.childNodes:
if b.nodeType == b.TEXT_NODE:
return b.data
def GetHome():
home = None
try:
home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
except:
pass
if (home == None) or (home.startswith("/") == False):
home = "/home"
return home
def ChangeOwner(filepath, user):
p = None
try:
p = pwd.getpwnam(user)
except:
pass
if p != None:
os.chown(filepath, p[2], p[3])
def CreateDir(dirpath, user, mode):
try:
os.makedirs(dirpath, mode)
except:
pass
ChangeOwner(dirpath, user)
def CreateAccount(user, password, expiration, thumbprint):
if IsWindows():
Log("Skipping CreateAccount on Windows")
return None
userentry = None
try:
userentry = pwd.getpwnam(user)
except:
pass
uidmin = None
try:
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
except:
pass
if uidmin == None:
uidmin = 100
if userentry != None and userentry[2] < uidmin:
Error("CreateAccount: " + user + " is a system user. Will not set password.")
return "Failed to set password for system user: " + user + " (0x06)."
if userentry == None:
command = "useradd -m " + user
if expiration != None:
command += " -e " + expiration.split('.')[0]
if Run(command):
Error("Failed to create user account: " + user)
return "Failed to create user account: " + user + " (0x07)."
else:
Log("CreateAccount: " + user + " already exists. Will update password.")
if password != None:
os.popen("chpasswd", "w").write(user + ":" + password + "\n")
try:
if password == None:
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
else:
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
os.chmod("/etc/sudoers.d/waagent", 0440)
except:
Error("CreateAccount: Failed to configure sudo access for user.")
return "Failed to configure sudo privileges (0x08)."
home = GetHome()
if thumbprint != None:
dir = home + "/" + user + "/.ssh"
CreateDir(dir, user, 0700)
pub = dir + "/id_rsa.pub"
prv = dir + "/id_rsa"
Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
for f in [pub, prv]:
os.chmod(f, 0600)
ChangeOwner(f, user)
SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
ChangeOwner(dir + "/authorized_keys", user)
Log("Created user account: " + user)
return None
def DeleteAccount(user):
if IsWindows():
Log("Skipping DeleteAccount on Windows")
return
userentry = None
try:
userentry = pwd.getpwnam(user)
except:
pass
if userentry == None:
Error("DeleteAccount: " + user + " not found.")
return
uidmin = None
try:
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
except:
pass
if uidmin == None:
uidmin = 100
if userentry[2] < uidmin:
Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
return
Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
Run("userdel -f -r " + user)
try:
os.remove("/etc/sudoers.d/waagent")
except:
pass
return
def ReloadSshd():
name = None
if IsRedHat() or IsSuse():
name = "sshd"
if IsDebian():
name = "ssh"
if name == None:
return
if not Run("service " + name + " status | grep running"):
Run("service " + name + " reload")
def IsInRangeInclusive(a, low, high):
return (a >= low and a <= high)
def IsPrintable(ch):
return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
def HexDump(buffer, size):
if size < 0:
size = len(buffer)
result = ""
for i in range(0, size):
if (i % 16) == 0:
result += "%06X: " % i
byte = struct.unpack("B", buffer[i])[0]
result += "%02X " % byte
if (i & 15) == 7:
result += " "
if ((i + 1) % 16) == 0 or (i + 1) == size:
j = i
while ((j + 1) % 16) != 0:
result += " "
if (j & 7) == 7:
result += " "
j += 1
result += " "
for j in range(i - (i % 16), i + 1):
byte = struct.unpack("B", buffer[j])[0]
k = '.'
if IsPrintable(byte):
k = chr(byte)
result += k
if (i + 1) != size:
result += "\n"
return result
def ThrottleLog(counter):
# Log everything up to 10, every 10 up to 100, then every 100.
return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
def Logger():
class T(object):
def __init__(self):
self.File = None
self = T()
def LogToFile(message):
FilePath = ["/var/log/waagent.log", "waagent.log"][IsWindows()]
if not os.path.isfile(FilePath) and self.File != None:
self.File.close()
self.File = None
if self.File == None:
self.File = open(FilePath, "a")
self.File.write(message + "\n")
self.File.flush()
def Log(message):
LogWithPrefix("", message)
def LogWithPrefix(prefix, message):
t = time.localtime()
t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
t += prefix
for line in message.split('\n'):
line = t + line
print(line)
LogToFile(line)
return Log, LogWithPrefix
Log, LogWithPrefix = Logger()
def NoLog(message):
pass
def LogIfVerbose(message):
if Verbose == True:
Log(message)
def LogWithPrefixIfVerbose(prefix, message):
if Verbose == True:
LogWithPrefix(prefix, message)
def Warn(message):
LogWithPrefix("WARNING:", message)
def Error(message):
LogWithPrefix("ERROR:", message)
def ErrorWithPrefix(prefix, message):
LogWithPrefix("ERROR:" + prefix, message)
def Linux_ioctl_GetIpv4Address(ifname):
import fcntl
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
def Linux_ioctl_GetInterfaceMac(ifname):
import fcntl
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
return ''.join(['%02X' % Ord(char) for char in info[18:24]])
def GetIpv4Address():
if IsLinux():
for ifname in PossibleEthernetInterfaces:
try:
return Linux_ioctl_GetIpv4Address(ifname)
except IOError, e:
pass
else:
try:
return socket.gethostbyname(socket.gethostname())
except Exception, e:
ErrorWithPrefix("GetIpv4Address:", str(e))
ErrorWithPrefix("GetIpv4Address:", traceback.format_exc())
def HexStringToByteArray(a):
b = ""
for c in range(0, len(a) / 2):
b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
return b
def GetMacAddress():
if IsWindows():
# Windows: Physical Address. . . . . . . . . : 00-15-17-79-00-7F\n
a = "ipconfig /all | findstr /c:\"Physical Address\" | findstr /v \"00-00-00-00-00-00-00\""
a = os.popen(a).read()
a = re.sub("\s+$", "", a)
a = re.sub(".+ ", "", a)
a = re.sub(":", "", a)
a = re.sub("-", "", a)
else:
for ifname in PossibleEthernetInterfaces:
try:
a = Linux_ioctl_GetInterfaceMac(ifname)
break
except IOError, e:
pass
return HexStringToByteArray(a)
def DeviceForIdePort(n):
if n > 3:
return None
g0 = "00000000"
if n > 1:
g0 = "00000001"
n = n - 2
device = None
path = "/sys/bus/vmbus/devices/"
for vmbus in os.listdir(path):
guid = GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
if guid[0] == g0 and guid[1] == "000" + str(n):
for root, dirs, files in os.walk(path + vmbus):
if root.endswith("/block"):
device = dirs[0]
break
break
return device
class Util(object):
def _HttpGet(self, url, headers):
LogIfVerbose("HttpGet(" + url + ")")
maxRetry = 2
if url.startswith("http://"):
url = url[7:]
url = url[url.index("/"):]
for retry in range(0, maxRetry + 1):
strRetry = str(retry)
log = [NoLog, Log][retry > 0]
log("retry HttpGet(" + url + "),retry=" + strRetry)
response = None
strStatus = "None"
try:
httpConnection = httplib.HTTPConnection(self.Endpoint)
if headers == None:
request = httpConnection.request("GET", url)
else:
request = httpConnection.request("GET", url, None, headers)
response = httpConnection.getresponse()
strStatus = str(response.status)
except:
pass
log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
if response == None or response.status != httplib.OK:
Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
if retry == maxRetry:
Log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
return None
else:
Log("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
time.sleep(10)
else:
log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
return response.read()
def HttpGetWithoutHeaders(self, url):
return self._HttpGet(url, None)
def HttpGetWithHeaders(self, url):
return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion})
def HttpSecureGetWithHeaders(self, url, transportCert):
return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName,
"x-ms-version": ProtocolVersion,
"x-ms-cipher-name": "DES_EDE3_CBC",
"x-ms-guest-agent-public-x509-cert": transportCert})
def HttpPost(self, url, data):
LogIfVerbose("HttpPost(" + url + ")")
maxRetry = 2
for retry in range(0, maxRetry + 1):
strRetry = str(retry)
log = [NoLog, Log][retry > 0]
log("retry HttpPost(" + url + "),retry=" + strRetry)
response = None
strStatus = "None"
try:
httpConnection = httplib.HTTPConnection(self.Endpoint)
request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName,
"Content-Type": "text/xml; charset=utf-8",
"x-ms-version": ProtocolVersion})
response = httpConnection.getresponse()
strStatus = str(response.status)
except:
pass
log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED):
Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
if retry == maxRetry:
Log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
return None
else:
Log("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
time.sleep(10)
else:
log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
return response
def LoadBalancerProbeServer(port):
class T(object):
def __init__(self, ip, port):
self.ProbeCounter = 0
self.server = SocketServer.TCPServer((ip, port), TCPHandler)
self.server_thread = threading.Thread(target = self.server.serve_forever)
self.server_thread.setDaemon(True)
self.server_thread.start()
def shutdown(self):
self.server.shutdown()
class TCPHandler(SocketServer.BaseRequestHandler):
def GetHttpDateTimeNow(self):
# Date: Fri, 25 Mar 2011 04:53:10 GMT
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
def handle(self):
context.ProbeCounter = (context.ProbeCounter + 1) % 1000000
log = [NoLog, LogIfVerbose][ThrottleLog(context.ProbeCounter)]
strCounter = str(context.ProbeCounter)
if context.ProbeCounter == 1:
Log("Receiving LB probes.")
log("Received LB probe # " + strCounter)
self.request.recv(1024)
self.request.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: text/html\r\nDate: " + self.GetHttpDateTimeNow() + "\r\n\r\nOK")
for retry in range(1,6):
context=None
ip = GetIpv4Address()
if ip == None :
Log("LoadBalancerProbeServer: GetIpv4Address() returned None, sleeping 10 before retry " + str(retry+1) )
time.sleep(10)
else:
try:
context = T(ip,port)
break
except Exception, e:
Log("LoadBalancerProbeServer: Exception contructing socket server: " + str(e))
Log("LoadBalancerProbeServer: Retry socket server construction #" + str(retry+1) )
return context
class ConfigurationProvider(object):
def __init__(self):
self.values = dict()
if os.path.isfile("/etc/waagent.conf") == False:
raise Exception("Missing configuration in /etc/waagent.conf")
try:
for line in GetFileContents("/etc/waagent.conf").split('\n'):
if not line.startswith("#") and "=" in line:
parts = line.split()[0].split('=')
value = parts[1].strip("\" ")
if value != "None":
self.values[parts[0]] = value
else:
self.values[parts[0]] = None
except:
Error("Unable to parse /etc/waagent.conf")
raise
return
def get(self, key):
return self.values.get(key)
class EnvMonitor(object):
def __init__(self):
self.shutdown = False
self.HostName = socket.gethostname()
self.server_thread = threading.Thread(target = self.monitor)
self.server_thread.setDaemon(True)
self.server_thread.start()
self.published = False
def monitor(self):
publish = Config.get("Provisioning.MonitorHostName")
dhcpcmd = "pidof dhclient"
if IsSuse():
dhcpcmd = "pidof dhcpcd"
if IsDebian():
dhcpcmd = "pidof dhclient3"
dhcppid = os.popen(dhcpcmd).read()
while not self.shutdown:
for a in RulesFiles:
if os.path.isfile(a):
if os.path.isfile(GetLastPathElement(a)):
os.remove(GetLastPathElement(a))
shutil.move(a, ".")
Log("EnvMonitor: Moved " + a + " -> " + LibDir)
if publish != None and publish.lower().startswith("y"):
try:
if socket.gethostname() != self.HostName:
Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
self.HostName = socket.gethostname()
WaAgent.UpdateAndPublishHostName(self.HostName)
dhcppid = os.popen(dhcpcmd).read()
self.published = True
except:
pass
else:
self.published = True
pid = ""
if not os.path.isdir("/proc/" + dhcppid.strip()):
pid = os.popen(dhcpcmd).read()
if pid != "" and pid != dhcppid:
Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
WaAgent.RestoreRoutes()
dhcppid = pid
for child in Children:
if child.poll() != None:
Children.remove(child)
time.sleep(5)
def SetHostName(self, name):
if socket.gethostname() == name:
self.published = True
elif Run("hostname " + name):
Error("Error: SetHostName: Cannot set hostname to " + name)
return ("Error: SetHostName: Cannot set hostname to " + name)
def IsNamePublished(self):
return self.published
def ShutdownService(self):
self.shutdown = True
self.server_thread.join()
class Certificates(object):
#
#
# 2010-12-15
# 2
# Pkcs7BlobWithPfxContents
# MIILTAY...
#
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
self.Incarnation = None
self.Role = None
def Parse(self, xmlText):
self.reinitialize()
SetFileContents("Certificates.xml", xmlText)
dom = xml.dom.minidom.parseString(xmlText)
for a in [ "CertificateFile", "Version", "Incarnation",
"Format", "Data", ]:
if not dom.getElementsByTagName(a):
Error("Certificates.Parse: Missing " + a)
return None
node = dom.childNodes[0]
if node.localName != "CertificateFile":
Error("Certificates.Parse: root not CertificateFile")
return None
SetFileContents("Certificates.p7m",
"MIME-Version: 1.0\n"
+ "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
+ "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
+ "Content-Transfer-Encoding: base64\n\n"
+ GetNodeTextData(dom.getElementsByTagName("Data")[0]))
if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
Error("Certificates.Parse: Failed to extract certificates from CMS message.")
return self
# There may be multiple certificates in this package. Split them.
file = open("Certificates.pem")
pindex = 1
cindex = 1
output = open("temp.pem", "w")
for line in file.readlines():
output.write(line)
if re.match(r'[-]+END .*?(KEY|CERTIFICATE)[-]+$',line):
output.close()
if re.match(r'[-]+END .*?KEY[-]+$',line):
os.rename("temp.pem", str(pindex) + ".prv")
pindex += 1
else:
os.rename("temp.pem", str(cindex) + ".crt")
cindex += 1
output = open("temp.pem", "w")
output.close()
os.remove("temp.pem")
keys = dict()
index = 1
filename = str(index) + ".crt"
while os.path.isfile(filename):
thumbprint = os.popen(Openssl + " x509 -in " + filename + " -fingerprint -noout").read().rstrip().split('=')[1].replace(':', '').upper()
pubkey=os.popen(Openssl + " x509 -in " + filename + " -pubkey -noout").read()
keys[pubkey] = thumbprint
os.rename(filename, thumbprint + ".crt")
os.chmod(thumbprint + ".crt", 0600)
if IsRedHat():
Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + thumbprint + ".crt")
index += 1
filename = str(index) + ".crt"
index = 1
filename = str(index) + ".prv"
while os.path.isfile(filename):
pubkey = os.popen(Openssl + " rsa -in " + filename + " -pubout").read()
os.rename(filename, keys[pubkey] + ".prv")
os.chmod(keys[pubkey] + ".prv", 0600)
if IsRedHat():
Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + keys[pubkey] + ".prv")
index += 1
filename = str(index) + ".prv"
return self
class SharedConfig(object):
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
self.Deployment = None
self.Incarnation = None
self.Role = None
self.LoadBalancerSettings = None
self.OutputEndpoints = None
self.Instances = None
def Parse(self, xmlText):
self.reinitialize()
SetFileContents("SharedConfig.xml", xmlText)
dom = xml.dom.minidom.parseString(xmlText)
for a in [ "SharedConfig", "Deployment", "Service",
"ServiceInstance", "Incarnation", "Role", ]:
if not dom.getElementsByTagName(a):
Error("SharedConfig.Parse: Missing " + a)
return None
node = dom.childNodes[0]
if node.localName != "SharedConfig":
Error("SharedConfig.Parse: root not SharedConfig")
return None
program = Config.get("Role.TopologyConsumer")
if program != None:
Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"]))
return self
class HostingEnvironmentConfig(object):
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
self.StoredCertificates = None
self.Deployment = None
self.Incarnation = None
self.Role = None
self.HostingEnvironmentSettings = None
self.ApplicationSettings = None
self.Certificates = None
self.ResourceReferences = None
def Parse(self, xmlText):
self.reinitialize()
SetFileContents("HostingEnvironmentConfig.xml", xmlText)
dom = xml.dom.minidom.parseString(xmlText)
for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
"ServiceInstance", "Incarnation", "Role", ]:
if not dom.getElementsByTagName(a):
Error("HostingEnvironmentConfig.Parse: Missing " + a)
return None
node = dom.childNodes[0]
if node.localName != "HostingEnvironmentConfig":
Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
return None
self.ApplicationSettings = dom.getElementsByTagName("Setting")
self.Certificates = dom.getElementsByTagName("StoredCertificate")
return self
def DecryptPassword(self, e):
SetFileContents("password.p7m",
"MIME-Version: 1.0\n"
+ "Content-Disposition: attachment; filename=\"password.p7m\"\n"
+ "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
+ "Content-Transfer-Encoding: base64\n\n"
+ textwrap.fill(e, 64))
return os.popen(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem").read()
def ActivateResourceDisk(self):
global DiskActivated
if IsWindows():
DiskActivated = True
Log("Skipping ActivateResourceDisk on Windows")
return
format = Config.get("ResourceDisk.Format")
if format == None or format.lower().startswith("n"):
DiskActivated = True
return
device = DeviceForIdePort(1)
if device == None:
Error("ActivateResourceDisk: Unable to detect disk topology.")
return
device = "/dev/" + device
for entry in os.popen("mount").read().split():
if entry.startswith(device + "1"):
Log("ActivateResourceDisk: " + device + "1 is already mounted.")
DiskActivated = True
return
mountpoint = Config.get("ResourceDisk.MountPoint")
if mountpoint == None:
mountpoint = "/mnt/resource"
CreateDir(mountpoint, "root", 0755)
fs = Config.get("ResourceDisk.Filesystem")
if fs == None:
fs = "ext3"
if os.popen("sfdisk -q -c " + device + " 1").read().rstrip() == "7" and fs != "ntfs":
Run("sfdisk -c " + device + " 1 83")
Run("mkfs." + fs + " " + device + "1")
if Run("mount " + device + "1 " + mountpoint):
Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).")
return
Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs)
DiskActivated = True
swap = Config.get("ResourceDisk.EnableSwap")
if swap == None or swap.lower().startswith("n"):
return
sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
os.remove(mountpoint + "/swapfile")
if not os.path.isfile(mountpoint + "/swapfile"):
Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
Run("mkswap " + mountpoint + "/swapfile")
if not Run("swapon " + mountpoint + "/swapfile"):
Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
else:
Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
def Process(self):
if DiskActivated == False:
diskThread = threading.Thread(target = self.ActivateResourceDisk)
diskThread.start()
User = None
Pass = None
Expiration = None
Thumbprint = None
for b in self.ApplicationSettings:
sname = b.getAttribute("name")
svalue = b.getAttribute("value")
if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword":
Pass = self.DecryptPassword(svalue)
elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername":
User = svalue
elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration":
Expiration = svalue
elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption":
Thumbprint = svalue.split(':')[1].upper()
if User != None and Pass != None:
if User != "root" and User != "" and Pass != "":
CreateAccount(User, Pass, Expiration, Thumbprint)
else:
Error("Not creating user account: " + User)
for c in self.Certificates:
cname = c.getAttribute("name")
csha1 = c.getAttribute("certificateId").split(':')[1].upper()
cpath = c.getAttribute("storeName")
clevel = c.getAttribute("configurationLevel")
if os.path.isfile(csha1 + ".prv"):
Log("Private key with thumbprint: " + csha1 + " was retrieved.")
if os.path.isfile(csha1 + ".crt"):
Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
program = Config.get("Role.ConfigurationConsumer")
if program != None:
Children.append(subprocess.Popen([program, LibDir + "/HostingEnvironmentConfig.xml"]))
class GoalState(Util):
#
#
# 2010-12-15
# 1
#
# Started
#
# 16001
#
#
#
# c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2
#
#
# MachineRole_IN_0
# Started
#
# http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=hostingEnvironmentConfig&incarnation=1
# http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=sharedConfig&incarnation=1
# http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&incarnation=1
#
#
#
#
#
#
# There is only one Role for VM images.
#
# Of primary interest is:
# Machine/ExpectedState -- this is how shutdown is requested
# LBProbePorts -- an http server needs to run here
# We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
# And of course, Incarnation
#
def __init__(self, Agent):
self.Agent = Agent
self.Endpoint = Agent.Endpoint
self.TransportCert = Agent.TransportCert
self.reinitialize()
def reinitialize(self):
self.Incarnation = None # integer
self.ExpectedState = None # "Started" or "Stopped"
self.HostingEnvironmentConfigUrl = None
self.HostingEnvironmentConfigXml = None
self.HostingEnvironmentConfig = None
self.SharedConfigUrl = None
self.SharedConfigXml = None
self.SharedConfig = None
self.CertificatesUrl = None
self.CertificatesXml = None
self.Certificates = None
self.RoleInstanceId = None
self.ContainerId = None
self.LoadBalancerProbePort = None # integer, ?list of integers
def Parse(self, xmlText):
self.reinitialize()
node = xml.dom.minidom.parseString(xmlText).childNodes[0]
if node.localName != "GoalState":
Error("GoalState.Parse: root not GoalState")
return None
for a in node.childNodes:
if a.nodeType == node.ELEMENT_NODE:
if a.localName == "Incarnation":
self.Incarnation = GetNodeTextData(a)
elif a.localName == "Machine":
for b in a.childNodes:
if b.nodeType == node.ELEMENT_NODE:
if b.localName == "ExpectedState":
self.ExpectedState = GetNodeTextData(b)
Log("ExpectedState: " + self.ExpectedState)
elif b.localName == "LBProbePorts":
for c in b.childNodes:
if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
self.LoadBalancerProbePort = int(GetNodeTextData(c))
elif a.localName == "Container":
for b in a.childNodes:
if b.nodeType == node.ELEMENT_NODE:
if b.localName == "ContainerId":
self.ContainerId = GetNodeTextData(b)
Log("ContainerId: " + self.ContainerId)
elif b.localName == "RoleInstanceList":
for c in b.childNodes:
if c.localName == "RoleInstance":
for d in c.childNodes:
if d.nodeType == node.ELEMENT_NODE:
if d.localName == "InstanceId":
self.RoleInstanceId = GetNodeTextData(d)
Log("RoleInstanceId: " + self.RoleInstanceId)
elif d.localName == "State":
pass
elif d.localName == "Configuration":
for e in d.childNodes:
if e.nodeType == node.ELEMENT_NODE:
if e.localName == "HostingEnvironmentConfig":
self.HostingEnvironmentConfigUrl = GetNodeTextData(e)
LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl)
self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
elif e.localName == "SharedConfig":
self.SharedConfigUrl = GetNodeTextData(e)
LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
elif e.localName == "Certificates":
self.CertificatesUrl = GetNodeTextData(e)
LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl)
self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
self.Certificates = Certificates().Parse(self.CertificatesXml)
if self.Incarnation == None:
Error("GoalState.Parse: Incarnation missing")
return None
if self.ExpectedState == None:
Error("GoalState.Parse: ExpectedState missing")
return None
if self.RoleInstanceId == None:
Error("GoalState.Parse: RoleInstanceId missing")
return None
if self.ContainerId == None:
Error("GoalState.Parse: ContainerId missing")
return None
SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
return self
def Process(self):
self.HostingEnvironmentConfig.Process()
class OvfEnv(object):
#
#
#
#
# 1.0
#
# LinuxProvisioningConfiguration
# HostName
# UserName
# UserPassword
# false
#
#
#
# EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62
# $HOME/UserName/.ssh/authorized_keys
#
#
#
#
# EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62
# $HOME/UserName/.ssh/id_rsa
#
#
#
#
#
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
self.WaNs = "http://schemas.microsoft.com/windowsazure"
self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
self.MajorVersion = 1
self.MinorVersion = 0
self.ComputerName = None
self.AdminPassword = None
self.UserName = None
self.UserPassword = None
self.DisableSshPasswordAuthentication = True
self.SshPublicKeys = []
self.SshKeyPairs = []
def Parse(self, xmlText):
self.reinitialize()
dom = xml.dom.minidom.parseString(xmlText)
if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
Error("Unable to parse OVF XML.")
section = None
newer = False
for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
for n in p.childNodes:
if n.localName == "Version":
verparts = GetNodeTextData(n).split('.')
major = int(verparts[0])
minor = int(verparts[1])
if major > self.MajorVersion:
newer = True
if major != self.MajorVersion:
break
if minor > self.MinorVersion:
newer = True
section = p
if newer == True:
Warn("Newer provisioning configuration detected. Please consider updating waagent.")
if section == None:
Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
return None
self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
try:
self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
except:
pass
disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
if len(disableSshPass) != 0:
self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true")
for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"):
fp = None
path = None
for c in pkey.childNodes:
if c.localName == "Fingerprint":
fp = GetNodeTextData(c).upper()
if c.localName == "Path":
path = GetNodeTextData(c)
self.SshPublicKeys += [[fp, path]]
for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
fp = None
path = None
for c in keyp.childNodes:
if c.localName == "Fingerprint":
fp = GetNodeTextData(c).upper()
if c.localName == "Path":
path = GetNodeTextData(c)
self.SshKeyPairs += [[fp, path]]
return self
def PrepareDir(self, filepath):
home = GetHome()
# Expand HOME variable if present in path
path = os.path.normpath(filepath.replace("$HOME", home))
if (path.startswith("/") == False) or (path.endswith("/") == True):
return None
dir = path.rsplit('/', 1)[0]
if dir != "":
CreateDir(dir, "root", 0700)
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
ChangeOwner(dir, self.UserName)
return path
def NumberToBytes(self, i):
result = []
while i:
result.append(chr(i & 0xFF))
i >>= 8
result.reverse()
return ''.join(result)
def BitsToString(self, a):
index=7
s = ""
c = 0
for bit in a:
c = c | (bit << index)
index = index - 1
if index == -1:
s = s + struct.pack('>B', c)
c = 0
index = 7
return s
def OpensslToSsh(self, file):
from pyasn1.codec.der import decoder as der_decoder
try:
f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
n=k[0]
e=k[1]
keydata=""
keydata += struct.pack('>I',len("ssh-rsa"))
keydata += "ssh-rsa"
keydata += struct.pack('>I',len(self.NumberToBytes(e)))
keydata += self.NumberToBytes(e)
keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
keydata += "\0"
keydata += self.NumberToBytes(n)
except Exception, e:
print("OpensslToSsh: Exception " + str(e))
return None
return "ssh-rsa " + base64.b64encode(keydata) + "\n"
def Process(self):
error = None
if self.ComputerName == None :
return "Error: Hostname missing"
error=WaAgent.EnvMonitor.SetHostName(self.ComputerName)
if error: return error
if self.DisableSshPasswordAuthentication:
filepath = "/etc/ssh/sshd_config"
# Disable RFC 4252 and RFC 4256 authentication schemes.
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
(a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
GetFileContents(filepath).split('\n'))) + "PasswordAuthentication no\nChallengeResponseAuthentication no\n")
Log("Disabled SSH password-based authentication methods.")
if self.AdminPassword != None:
os.popen("chpasswd", "w").write("root:" + self.AdminPassword + "\n")
if self.UserName != None:
error = CreateAccount(self.UserName, self.UserPassword, None, None)
sel = os.popen("getenforce").read().startswith("Enforcing")
if sel == True and IsRedHat():
Run("setenforce 0")
home = GetHome()
for pkey in self.SshPublicKeys:
if not os.path.isfile(pkey[0] + ".crt"):
Error("PublicKey not found: " + pkey[0])
error = "Failed to deploy public key (0x09)."
continue
path = self.PrepareDir(pkey[1])
if path == None:
Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
error = "Invalid path for public key (0x03)."
continue
Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
if IsRedHat():
Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + pkey[0] + ".pub")
if IsUbuntu():
# Only supported in new SSH releases
Run("ssh-keygen -i -m PKCS8 -f " + pkey[0] + ".pub >> " + path)
else:
SshPubKey = self.OpensslToSsh(pkey[0] + ".pub")
if SshPubKey != None:
AppendFileContents(path, SshPubKey)
else:
Error("Failed: " + pkey[0] + ".crt -> " + path)
error = "Failed to deploy public key (0x04)."
if IsRedHat():
Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
ChangeOwner(path, self.UserName)
for keyp in self.SshKeyPairs:
if not os.path.isfile(keyp[0] + ".prv"):
Error("KeyPair not found: " + keyp[0])
error = "Failed to deploy key pair (0x0A)."
continue
path = self.PrepareDir(keyp[1])
if path == None:
Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
error = "Invalid path for key pair (0x05)."
continue
SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
os.chmod(path, 0600)
Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
if IsRedHat():
Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path + ".pub")
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
ChangeOwner(path, self.UserName)
ChangeOwner(path + ".pub", self.UserName)
if sel == True and IsRedHat():
Run("setenforce 1")
while not WaAgent.EnvMonitor.IsNamePublished():
time.sleep(1)
ReloadSshd()
return error
def UpdateAndPublishHostNameCommon(name):
# RedHat
if IsRedHat():
filepath = "/etc/sysconfig/network"
if os.path.isfile(filepath):
ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n"
+ "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n'))))
for ethernetInterface in PossibleEthernetInterfaces:
filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
if os.path.isfile(filepath):
ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
+ "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
# Debian
if IsDebian():
SetFileContents("/etc/hostname", name)
for filepath in EtcDhcpClientConfFiles:
if os.path.isfile(filepath):
ReplaceFileContentsAtomic(filepath, "send host-name \"" + name + "\";\n"
+ "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents(filepath).split('\n'))))
# Suse
if IsSuse():
SetFileContents("/etc/HOSTNAME", name)
class Agent(Util):
def __init__(self):
self.GoalState = None
self.Endpoint = None
self.LoadBalancerProbeServer = None
self.HealthReportCounter = 0
self.TransportCert = ""
self.EnvMonitor = None
self.SendData = None
self.DhcpResponse = None
def CheckVersions(self):
#
#
#
# 2010-12-15
#
#
# 2010-12-15
# 2010-28-10
#
#
global ProtocolVersion
protocolVersionSeen = False
node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
if node.localName != "Versions":
Error("CheckVersions: root not Versions")
return False
for a in node.childNodes:
if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
for b in a.childNodes:
if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
v = GetNodeTextData(b)
LogIfVerbose("Fabric supported wire protocol version: " + v)
if v == ProtocolVersion:
protocolVersionSeen = True
if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
v = GetNodeTextData(a.getElementsByTagName("Version")[0])
LogIfVerbose("Fabric preferred wire protocol version: " + v)
if ProtocolVersion < v:
Warn("Newer wire protocol version detected. Please consider updating waagent.")
if not protocolVersionSeen:
Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
ProtocolVersion = "2011-08-31"
Log("Negotiated wire protocol version: " + ProtocolVersion)
return True
def Unpack(self, buffer, offset, range):
result = 0
for i in range:
result = (result << 8) | Ord(buffer[offset + i])
return result
def UnpackLittleEndian(self, buffer, offset, length):
return self.Unpack(buffer, offset, range(length - 1, -1, -1))
def UnpackBigEndian(self, buffer, offset, length):
return self.Unpack(buffer, offset, range(0, length))
def HexDump3(self, buffer, offset, length):
return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
def HexDump2(self, buffer):
return self.HexDump3(buffer, 0, len(buffer))
def BuildDhcpRequest(self):
#
# typedef struct _DHCP {
# UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
# UINT8 HardwareAddressType; /* htype: ethernet */
# UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
# UINT8 Hops; /* hops: 0 */
# UINT8 TransactionID[4]; /* xid: random */
# UINT8 Seconds[2]; /* secs: 0 */
# UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
# UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
# UINT8 YourIpAddress[4]; /* yiaddr: 0 */
# UINT8 ServerIpAddress[4]; /* siaddr: 0 */
# UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
# UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */
# UINT8 ServerName[64]; /* sname: 0 */
# UINT8 BootFileName[128]; /* file: 0 */
# UINT8 MagicCookie[4]; /* 99 130 83 99 */
# /* 0x63 0x82 0x53 0x63 */
# /* options -- hard code ours */
#
# UINT8 MessageTypeCode; /* 53 */
# UINT8 MessageTypeLength; /* 1 */
# UINT8 MessageType; /* 1 for DISCOVER */
# UINT8 End; /* 255 */
# } DHCP;
#
# tuple of 244 zeros
# (struct.pack_into would be good here, but requires Python 2.5)
sendData = [0] * 244
transactionID = os.urandom(4)
macAddress = GetMacAddress()
# Opcode = 1
# HardwareAddressType = 1 (ethernet/MAC)
# HardwareAddressLength = 6 (ethernet/MAC/48 bits)
for a in range(0, 3):
sendData[a] = [1, 1, 6][a]
# fill in transaction id (random number to ensure response matches request)
for a in range(0, 4):
sendData[4 + a] = Ord(transactionID[a])
LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
# fill in ClientHardwareAddress
for a in range(0, 6):
sendData[0x1C + a] = Ord(macAddress[a])
# DHCP Magic Cookie: 99, 130, 83, 99
# MessageTypeCode = 53 DHCP Message Type
# MessageTypeLength = 1
# MessageType = DHCPDISCOVER
# End = 255 DHCP_END
for a in range(0, 8):
sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
return array.array("c", map(chr, sendData))
def IntegerToIpAddressV4String(self, a):
return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
def RouteAdd(self, net, mask, gateway):
if IsWindows():
return
net = self.IntegerToIpAddressV4String(net)
mask = self.IntegerToIpAddressV4String(mask)
gateway = self.IntegerToIpAddressV4String(gateway)
Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway)
def HandleDhcpResponse(self, sendData, receiveBuffer):
LogIfVerbose("HandleDhcpResponse")
bytesReceived = len(receiveBuffer)
if bytesReceived < 0xF6:
Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
return None
LogIfVerbose("BytesReceived: " + hex(bytesReceived))
LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
# check transactionId, cookie, MAC address
# cookie should never mismatch
# transactionId and MAC address may mismatch if we see a response meant from another machine
for offsets in [range(4, 4 + 4), range(0x1C, 0x1C + 6), range(0xEC, 0xEC + 4)]:
for offset in offsets:
sentByte = Ord(sendData[offset])
receivedByte = Ord(receiveBuffer[offset])
if sentByte != receivedByte:
LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
return None
endpoint = None
#
# Walk all the returned options, parsing out what we need, ignoring the others.
# We need the custom option 245 to find the the endpoint we talk to,
# as well as, to handle some Linux DHCP client incompatibilities,
# options 3 for default gateway and 249 for routes. And 255 is end.
#
i = 0xF0 # offset to first option
while i < bytesReceived:
option = Ord(receiveBuffer[i])
length = 0
if (i + 1) < bytesReceived:
length = Ord(receiveBuffer[i + 1])
LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
if option == 255:
LogIfVerbose("DHCP packet ended at offset " + hex(i))
break
elif option == 249:
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
if length < 5:
Error("Data too small for option " + option)
j = i + 2
while j < (i + length + 2):
maskLengthBits = Ord(receiveBuffer[j])
maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
j += 1
net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
net <<= (32 - maskLengthBytes * 8)
net &= mask
j += maskLengthBytes
gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
j += 4
self.RouteAdd(net, mask, gateway)
if j != (i + length + 2):
Error("HandleDhcpResponse: Unable to parse routes")
elif option == 3 or option == 245:
if i + 5 < bytesReceived:
if length != 4:
Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
return None
gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
IpAddress = self.IntegerToIpAddressV4String(gateway)
if option == 3:
self.RouteAdd(0, 0, gateway)
name = "DefaultGateway"
else:
endpoint = IpAddress
name = "Windows Azure wire protocol endpoint"
LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
else:
Error("HandleDhcpResponse: Data too small for option " + option)
else:
LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
i += length + 2
return endpoint
def DoDhcpWork(self):
#
# Discover the wire server via DHCP option 245.
# And workaround incompatibility with Windows Azure DHCP servers.
#
ShortSleep = False # Sleep 1 second before retrying DHCP queries.
ifname=None
if not IsWindows():
Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT")
Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT")
sleepDurations = [0, 5, 10, 30, 60, 60, 60, 60]
maxRetry = len(sleepDurations)
lastTry = (maxRetry - 1)
for retry in range(0, maxRetry):
try:
strRetry = str(retry)
prefix = "DoDhcpWork: try=" + strRetry
LogIfVerbose(prefix)
sendData = self.BuildDhcpRequest()
LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
missingDefaultRoute = True
try:
for line in os.popen("route -n").read().split('\n'):
if line.startswith("0.0.0.0 "):
missingDefaultRoute = False
except:
pass
if missingDefaultRoute:
# This is required because sending after binding to 0.0.0.0 fails with
# network unreachable when the default gateway is not set up.
for i in PossibleEthernetInterfaces:
try:
if Linux_ioctl_GetIpv4Address(i):
ifname=i
except IOError, e:
pass
Log("DoDhcpWork: Missing default route - adding broadcast route for DHCP.")
Run("route add 255.255.255.255 dev " + ifname)
sock.bind(("0.0.0.0", 68))
sock.sendto(sendData, ("", 67))
sock.settimeout(10)
Log("DoDhcpWork: Setting socket.timeout=10, entering recv")
receiveBuffer = sock.recv(1024)
endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
if endpoint == None:
LogIfVerbose("DoDhcpWork: No endpoint found")
if endpoint != None or retry == lastTry:
if endpoint != None:
self.SendData = sendData
self.DhcpResponse = receiveBuffer
if retry == lastTry:
LogIfVerbose("DoDhcpWork: try=" + strRetry)
return endpoint
sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
time.sleep(sleepDuration)
except Exception, e:
ErrorWithPrefix(prefix, str(e))
ErrorWithPrefix(prefix, traceback.format_exc())
finally:
sock.close()
if missingDefaultRoute:
#We added this route - delete it
Run("route del 255.255.255.255 dev " + ifname)
Log("DoDhcpWork: Removing broadcast route for DHCP.")
return None
def UpdateAndPublishHostName(self, name):
# Set hostname locally and publish to iDNS
Log("Setting host name: " + name)
UpdateAndPublishHostNameCommon(name)
for ethernetInterface in PossibleEthernetInterfaces:
Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface)
self.RestoreRoutes()
def RestoreRoutes(self):
if self.SendData != None and self.DhcpResponse != None:
self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
def UpdateGoalState(self):
goalStateXml = None
maxRetry = 9
log = NoLog
for retry in range(1, maxRetry + 1):
strRetry = str(retry)
log("retry UpdateGoalState,retry=" + strRetry)
goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
if goalStateXml != None:
break
log = Log
time.sleep(retry)
if not goalStateXml:
Error("UpdateGoalState failed.")
return
Log("Retrieved GoalState from Windows Azure Fabric.")
self.GoalState = GoalState(self).Parse(goalStateXml)
return self.GoalState
def ReportReady(self):
counter = (self.HealthReportCounter + 1) % 1000000
self.HealthReportCounter = counter
healthReport = (""
+ self.GoalState.Incarnation
+ ""
+ self.GoalState.ContainerId
+ ""
+ self.GoalState.RoleInstanceId
+ "Ready")
a = self.HttpPost("/machine?comp=health", healthReport)
if a != None:
return a.getheader("x-ms-latest-goal-state-incarnation-number")
return None
def ReportNotReady(self, status, desc):
healthReport = (""
+ self.GoalState.Incarnation
+ ""
+ self.GoalState.ContainerId
+ ""
+ self.GoalState.RoleInstanceId
+ "NotReady"
+ "" + status + "" + desc + " "
+ "")
a = self.HttpPost("/machine?comp=health", healthReport)
if a != None:
return a.getheader("x-ms-latest-goal-state-incarnation-number")
return None
def ReportRoleProperties(self, thumbprint):
roleProperties = (""
+ "" + self.GoalState.ContainerId + ""
+ ""
+ "" + self.GoalState.RoleInstanceId + ""
+ ""
+ "")
a = self.HttpPost("/machine?comp=roleProperties", roleProperties)
Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
return a
def LoadBalancerProbeServer_Shutdown(self):
if self.LoadBalancerProbeServer != None:
self.LoadBalancerProbeServer.shutdown()
self.LoadBalancerProbeServer = None
def GenerateTransportCert(self):
Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
cert = ""
for line in GetFileContents("TransportCert.pem").split('\n'):
if not "CERTIFICATE" in line:
cert += line.rstrip()
return cert
def Provision(self):
if IsWindows():
Log("Skipping Provision on Windows")
return
enabled = Config.get("Provisioning.Enabled")
if enabled != None and enabled.lower().startswith("n"):
return
Log("Provisioning image started.")
type = Config.get("Provisioning.SshHostKeyPairType")
if type == None:
type = "rsa"
regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
Run("rm -f /etc/ssh/ssh_host_*key*")
Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key")
ReloadSshd()
SetFileContents(LibDir + "/provisioned", "")
dvd = "/dev/hdc"
if os.path.exists("/dev/sr0"):
dvd = "/dev/sr0"
modloaded=False
if Run("fdisk -l " + dvd + " | grep Disk"):
# Is it possible to load a module for ata_piix?
retcode,krn=RunSafe('uname -r')
if retcode:
Error("Unable to provision: Failed to call uname -a")
return "Unable to provision: Failed to mount DVD."
krn_pth='/lib/modules/'+krn+'/kernel/drivers/ata/ata_piix.ko'
if not os.path.isfile(krn_pth):
Error("Unable to provision: Failed to locate ata_piix.ko")
return "Unable to provision: Failed to mount DVD."
retcode,output=RunSafe('insmod ' + krn_pth)
if retcode:
Error("Unable to provision: Failed to insmod " + krn+pth)
return "Failed to retrieve provisioning data (0x01)."
modloaded=True
Log("Provision: Loaded " + krn_pth + " driver for ATAPI CD-ROM")
# we have succeeded loading the ata_piix mod
for i in range(10): # we may have to wait
if os.path.exists("/dev/sr0"):
dvd = "/dev/sr0"
break
Log("Waiting for DVD - sleeping 1 - "+str(i+1)+" try...")
time.sleep(1)
CreateDir("/mnt/cdrom/secure", "root", 0700)
#begin mount loop - ten tries - 5 sec wait between
for retry in range(1,11):
retcode,output=RunSafe("mount -v " + dvd + " /mnt/cdrom/secure")
Log(output)
if retcode:
Log("mount failed on attempt #" + str(retry) )
else:
Log("mount suceed on attempt #" + str(retry) )
break
Log("mount loop sleeping 5...")
time.sleep(5)
if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
Error("Unable to provision: Missing ovf-env.xml on DVD.")
return "Failed to retrieve provisioning data (0x02)."
ovfxml = GetFileContents("/mnt/cdrom/secure/ovf-env.xml")
SetFileContents("ovf-env.xml", re.sub(".*?<", "*<", ovfxml))
Run("umount /mnt/cdrom/secure")
if modloaded:
Run('rmmod ' + krn_pth)
error = None
if ovfxml != None:
Log("Provisioning image using OVF settings in the DVD.")
ovfobj = OvfEnv().Parse(ovfxml)
if ovfobj != None:
error = ovfobj.Process()
if error :
Error ("Provisioninig image FAILED " + error)
return ("Provisioninig image FAILED " + error)
# This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml
fingerprint = os.popen("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub").read().rstrip().split()[1].replace(':','')
self.ReportRoleProperties(fingerprint)
delRootPass = Config.get("Provisioning.DeleteRootPassword")
if delRootPass != None and delRootPass.lower().startswith("y"):
DeleteRootPassword()
Log("Provisioning image completed.")
return error
def Run(self):
if IsLinux():
SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
if GetIpv4Address() == None:
Log("Waiting for network.")
while(GetIpv4Address() == None):
time.sleep(10)
Log("IPv4 address: " + GetIpv4Address())
Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in GetMacAddress()]))
# Consume Entropy in ACPI table provided by Hyper-V
try:
SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
except:
pass
Log("Probing for Windows Azure environment.")
self.Endpoint = self.DoDhcpWork()
if self.Endpoint == None:
Log("Windows Azure environment not detected.")
while True:
time.sleep(60)
Log("Discovered Windows Azure endpoint: " + self.Endpoint)
if not self.CheckVersions():
Error("Agent.CheckVersions failed")
sys.exit(1)
self.EnvMonitor = EnvMonitor()
# Set SCSI timeout on root device
try:
timeout = Config.get("OS.RootDeviceScsiTimeout")
if timeout != None:
SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout)
except:
pass
global Openssl
Openssl = Config.get("OS.OpensslPath")
if Openssl == None:
Openssl = "openssl"
self.TransportCert = self.GenerateTransportCert()
incarnation = None # goalStateIncarnationFromHealthReport
currentPort = None # loadBalancerProbePort
goalState = None # self.GoalState, instance of GoalState
provisioned = os.path.exists(LibDir + "/provisioned")
program = Config.get("Role.StateConsumer")
provisionError = None
lbProbeResponder = True
setting = Config.get("LBProbeResponder")
if setting != None and setting.lower().startswith("n"):
lbProbeResponder = False
while True:
if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
goalState = self.UpdateGoalState()
if provisioned == False:
self.ReportNotReady("Provisioning", "Starting")
goalState.Process()
if provisioned == False:
provisionError = self.Provision()
provisioned = True
#
# only one port supported
# restart server if new port is different than old port
# stop server if no longer a port
#
goalPort = goalState.LoadBalancerProbePort
if currentPort != goalPort:
self.LoadBalancerProbeServer_Shutdown()
currentPort = goalPort
if currentPort != None and lbProbeResponder == True:
self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)
if self.LoadBalancerProbeServer == None :
lbProbeResponder = False
Log("Unable to create LBProbeResponder.")
if program != None and DiskActivated == True:
Children.append(subprocess.Popen([program, "Ready"]))
program = None
if goalState.ExpectedState == "Stopped":
program = Config.get("Role.StateConsumer")
if program != None:
Run(program + " Shutdown")
self.EnvMonitor.ShutdownService()
self.LoadBalancerProbeServer_Shutdown()
command = ["/sbin/shutdown -hP now", "shutdown /s /t 5"][IsWindows()]
Run(command)
return
sleepToReduceAccessDenied = 3
time.sleep(sleepToReduceAccessDenied)
if provisionError != None:
incarnation = self.ReportNotReady("ProvisioningFailed", provisionError)
else:
incarnation = self.ReportReady()
time.sleep(25 - sleepToReduceAccessDenied)
Init_Suse = """\
#! /bin/sh
### BEGIN INIT INFO
# Provides: WindowsAzureLinuxAgent
# Required-Start: $network sshd
# Required-Stop: $network sshd
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Description: Start the WindowsAzureLinuxAgent
### END INIT INFO
WAZD_BIN=/usr/sbin/waagent
test -x $WAZD_BIN || exit 5
case "$1" in
start)
echo "Starting WindowsAzureLinuxAgent"
## Start daemon with startproc(8). If this fails
## the echo return value is set appropriate.
startproc -f $WAZD_BIN -daemon
exit $?
;;
stop)
echo "Shutting down WindowsAzureLinuxAgent"
## Stop daemon with killproc(8) and if this fails
## set echo the echo return value.
killproc -p /var/run/waagent.pid $WAZD_BIN
exit $?
;;
try-restart)
## Stop the service and if this succeeds (i.e. the
## service was running before), start it again.
$0 status >/dev/null && $0 restart
;;
restart)
## Stop the service and regardless of whether it was
## running or not, start it again.
$0 stop
$0 start
;;
force-reload|reload)
;;
status)
echo -n "Checking for service WindowsAzureLinuxAgent "
## Check status with checkproc(8), if process is running
## checkproc will return with exit status 0.
checkproc -p $WAZD_PIDFILE $WAZD_BIN
exit $?
;;
probe)
;;
*)
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
exit 1
;;
esac
"""
Init_RedHat = """\
#!/bin/bash
#
# Init file for WindowsAzureLinuxAgent.
#
# chkconfig: 2345 60 80
# description: WindowsAzureLinuxAgent
#
# source function library
. /etc/rc.d/init.d/functions
RETVAL=0
FriendlyName="WindowsAzureLinuxAgent"
WAZD_BIN=/usr/sbin/waagent
start()
{
echo -n $"Starting $FriendlyName: "
$WAZD_BIN -daemon &
}
stop()
{
echo -n $"Stopping $FriendlyName: "
killproc -p /var/run/waagent.pid $WAZD_BIN
RETVAL=$?
echo
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
reload)
;;
report)
;;
status)
status $WAZD_BIN
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|restart|status}"
RETVAL=1
esac
exit $RETVAL
"""
Init_Ubuntu = """\
#walinuxagent - start Windows Azure agent
description "walinuxagent"
author "Ben Howard "
start on (filesystem and started rsyslog)
pre-start script
WALINUXAGENT_ENABLED=1
[ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent
if [ "$WALINUXAGENT_ENABLED" != "1" ]; then
exit 1
fi
if [ ! -x /usr/sbin/waagent ]; then
exit 1
fi
#Load the udf module
modprobe -b udf
end script
exec /usr/sbin/waagent -daemon
"""
Init_Debian = """\
#!/bin/sh
### BEGIN INIT INFO
# Provides: WindowsAzureLinuxAgent
# Required-Start: $network $syslog
# Required-Stop: $network $syslog
# Should-Start: $network $syslog
# Should-Stop: $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: WindowsAzureLinuxAgent
# Description: WindowsAzureLinuxAgent
### END INIT INFO
. /lib/lsb/init-functions
OPTIONS="-daemon"
WAZD_BIN=/usr/sbin/waagent
WAZD_PID=/var/run/waagent.pid
case "$1" in
start)
log_begin_msg "Starting WindowsAzureLinuxAgent..."
pid=$( pidofproc $WAZD_BIN )
if [ -n "$pid" ] ; then
log_begin_msg "Already running."
log_end_msg 0
exit 0
fi
start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
log_end_msg $?
;;
stop)
log_begin_msg "Stopping WindowsAzureLinuxAgent..."
start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
ret=$?
rm -f $WAZD_PID
log_end_msg $ret
;;
force-reload)
$0 restart
;;
restart)
$0 stop
$0 start
;;
status)
status_of_proc $WAZD_BIN && exit 0 || exit $?
;;
*)
log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
exit 1
;;
esac
exit 0
"""
WaagentConf = """\
#
# Windows Azure Linux Agent Configuration
#
Role.StateConsumer=None # Specified program is invoked with "Ready" or "Shutdown".
# Shutdown will be initiated only after the program returns. Windows Azure will
# power off the VM if shutdown is not completed within ?? minutes.
Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
Provisioning.Enabled=y #
Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Filesystem=ext4 #
ResourceDisk.MountPoint=/mnt/resource #
ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
Logs.Verbose=n #
OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
OS.OpensslPath=None # If "None", the system default version is used.
"""
WaagentLogrotate = """\
/var/log/waagent.log {
monthly
rotate 6
notifempty
missingok
}
"""
def AddToLinuxKernelCmdline(options):
if os.path.isfile("/boot/grub/menu.lst"):
Run("sed -i --follow-symlinks '/kernel/s|$| " + options + " |' /boot/grub/menu.lst")
filepath = "/etc/default/grub"
if os.path.isfile(filepath):
filecontents = GetFileContents(filepath).split('\n')
current = filter(lambda a: a.startswith("GRUB_CMDLINE_LINUX"), filecontents)
ReplaceFileContentsAtomic(filepath,
"\n".join(filter(lambda a: not a.startswith("GRUB_CMDLINE_LINUX"), filecontents))
+ current[0][:-1] + " " + options + "\"\n")
Run("update-grub")
def ApplyVNUMAWorkaround():
VersionParts = platform.release().replace('-', '.').split('.')
if int(VersionParts[0]) > 2:
return
if int(VersionParts[1]) > 6:
return
if int(VersionParts[2]) > 37:
return
AddToLinuxKernelCmdline("numa=off")
# TODO: This is not ideal for offline installation.
print("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
def RevertVNUMAWorkaround():
print("Automatic reverting of GRUB configuration is not yet supported. Please edit by hand.")
def Install():
if IsWindows():
print("ERROR: -install invalid for Windows.")
return 1
os.chmod(sys.argv[0], 0755)
SwitchCwd()
requiredDeps = [ "/sbin/route", "/sbin/shutdown" ]
if IsDebian():
requiredDeps += [ "/usr/sbin/update-rc.d" ]
if IsSuse():
requiredDeps += [ "/sbin/insserv" ]
for a in requiredDeps:
if not os.path.isfile(a):
Error("Missing required dependency: " + a)
return 1
missing = False
for a in [ "ssh-keygen", "useradd", "openssl", "sfdisk",
"fdisk", "mkfs", "chpasswd", "sed", "grep", "sudo" ]:
if Run("which " + a + " > /dev/null 2>&1"):
Warn("Missing dependency: " + a)
missing = True
if missing == True:
Warn("Please resolve missing dependencies listed for full functionality.")
if UsesRpm():
if not Run("rpm --quiet -q NetworkManager"):
Error(GuestAgentLongName + " is not compatible with NetworkManager.")
return 1
if Run("rpm --quiet -q python-pyasn1"):
Error(GuestAgentLongName + " requires python-pyasn1.")
return 1
if UsesDpkg() and not Run("dpkg-query -s network-manager >/dev/null 2>&1"):
Error(GuestAgentLongName + " is not compatible with network-manager.")
return 1
for a in RulesFiles:
if os.path.isfile(a):
if os.path.isfile(GetLastPathElement(a)):
os.remove(GetLastPathElement(a))
shutil.move(a, ".")
Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
if IsUbuntu():
# Support for Ubuntu's upstart configuration
filename="waagent.conf"
filepath = "/etc/init/" + filename
SetFileContents(filepath, Init_Ubuntu)
os.chmod(filepath, 0644)
else:
# Regular init.d configurations
filename = "waagent"
filepath = "/etc/init.d/" + filename
distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
if distro == 0:
Error("Unable to detect Linux Distribution.")
return 1
init = [[Init_RedHat, "chkconfig --add " + filename],
[Init_Debian, "update-rc.d " + filename + " defaults"],
[Init_Suse, "insserv " + filename]][distro - 1]
SetFileContents(filepath, init[0])
os.chmod(filepath, 0755)
Run(init[1])
if os.path.isfile("/etc/waagent.conf"):
try:
os.remove("/etc/waagent.conf.old")
except:
pass
try:
os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
except:
pass
SetFileContents("/etc/waagent.conf", WaagentConf)
SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
filepath = "/etc/ssh/sshd_config"
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
a.startswith("ClientAliveInterval"),
GetFileContents(filepath).split('\n'))) + "ClientAliveInterval 180\n")
Log("Configured SSH client probing to keep connections alive.")
ApplyVNUMAWorkaround()
return 0
def Uninstall():
if IsWindows():
print("ERROR: -uninstall invalid for windows, see waagent_service.exe")
return 1
SwitchCwd()
for a in RulesFiles:
if os.path.isfile(GetLastPathElement(a)):
try:
shutil.move(GetLastPathElement(a), a)
Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
except:
pass
filename = "waagent"
a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
if a == 0:
Error("Unable to detect Linux Distribution.")
return 1
Run("service " + filename + " stop")
cmd = ["chkconfig --del " + filename,
"update-rc.d -f " + filename + " remove",
"insserv -r " + filename][a - 1]
Run(cmd)
for f in os.listdir(LibDir) + ["/etc/init/waagent.conf","/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
try:
os.remove(f)
except:
pass
RevertVNUMAWorkaround()
return 0
def DeleteRootPassword():
filepath="/etc/shadow"
ReplaceFileContentsAtomic(filepath, "root:*LOCK*:14600::::::\n" + "\n".join(filter(lambda a: not
a.startswith("root:"),
GetFileContents(filepath).split('\n'))))
os.chmod(filepath, 0000)
if IsRedHat():
Run("chcon system_u:object_r:shadow_t:s0 " + filepath)
Log("Root password deleted.")
def Deprovision(force, deluser):
if IsWindows():
Run(os.environ["windir"] + "\\system32\\sysprep\\sysprep.exe /generalize")
return 0
SwitchCwd()
ovfxml = GetFileContents("ovf-env.xml")
ovfobj = None
if ovfxml != None:
ovfobj = OvfEnv().Parse(ovfxml)
print("WARNING! The waagent service will be stopped.")
print("WARNING! All SSH host key pairs will be deleted.")
if IsUbuntu():
print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.")
else:
print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
print("WARNING! Cached DHCP leases will be deleted.")
delRootPass = Config.get("Provisioning.DeleteRootPassword")
if delRootPass != None and delRootPass.lower().startswith("y"):
print("WARNING! root password will be disabled. You will not be able to login as root.")
if ovfobj != None and deluser == True:
print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
return 1
Run("service waagent stop")
if deluser == True:
DeleteAccount(ovfobj.UserName)
# Remove SSH host keys
regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
Run("rm -f /etc/ssh/ssh_host_*key*")
# Remove root password
if delRootPass != None and delRootPass.lower().startswith("y"):
DeleteRootPassword()
# Remove distribution specific networking configuration
UpdateAndPublishHostNameCommon("localhost.localdomain")
# RedHat, Suse, Debian
for a in VarLibDhcpDirectories:
Run("rm -f " + a + "/*")
# Clear LibDir, remove nameserver and root bash history
fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log" ]
if IsUbuntu():
# Ubuntu uses resolv.conf by default, so removing /etc/resolv.conf will
# break resolvconf. Therefore, we check to see if resolvconf is in use,
# and if so, we remove the resolvconf artifacts.
Log("Deprovision: Ubuntu specific resolv.conf behavior selected.")
if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
Log("resolvconf is not configured. Removing /etc/resolv.conf")
fileBlackList.append('/etc/resolv.conf')
else:
Log("resolvconf is enabled; leaving /etc/resolv.conf intact")
resolvConfD = '/etc/resolvconf/resolv.conf.d/'
fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'originial' ])
else:
fileBlackList.extend(os.listdir(LibDir) + ['/etc/resolv.conf'])
for f in os.listdir(LibDir) + fileBlackList:
try:
os.remove(f)
except:
pass
return 0
def SwitchCwd():
if not IsWindows():
CreateDir(LibDir, "root", 0700)
os.chdir(LibDir)
def Usage():
print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
return 0
if GuestAgentVersion == "":
print("WARNING! This is a non-standard agent that does not include a valid version string.")
if IsLinux() and not DetectLinuxDistro():
print("WARNING! Unable to detect Linux distribution. Some functionality may be broken.")
if len(sys.argv) == 1:
sys.exit(Usage())
args = []
force = False
for a in sys.argv[1:]:
if re.match("^([-/]*)(help|usage|\?)", a):
sys.exit(Usage())
elif re.match("^([-/]*)verbose", a):
Verbose = True
elif re.match("^([-/]*)force", a):
force = True
elif re.match("^([-/]*)(setup|install)", a):
sys.exit(Install())
elif re.match("^([-/]*)(uninstall)", a):
sys.exit(Uninstall())
else:
args.append(a)
Config = ConfigurationProvider()
verbose = Config.get("Logs.Verbose")
if verbose != None and verbose.lower().startswith("y"):
Verbose = True
daemon = False
for a in args:
if re.match("^([-/]*)deprovision\+user", a):
sys.exit(Deprovision(force, True))
elif re.match("^([-/]*)deprovision", a):
sys.exit(Deprovision(force, False))
elif re.match("^([-/]*)daemon", a):
daemon = True
elif re.match("^([-/]*)version", a):
print(GuestAgentVersion + " running on " + LinuxDistro)
sys.exit(0)
elif re.match("^([-/]*)serialconsole", a):
AddToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
Log("Configured kernel to use ttyS0 as the boot console.")
sys.exit(0)
else:
print("Invalid command line parameter:" + a)
sys.exit(1)
if daemon == False:
sys.exit(Usage())
try:
SwitchCwd()
Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
if IsLinux():
Log("Linux Distribution Detected : " + LinuxDistro)
WaAgent = Agent()
WaAgent.Run()
except Exception, e:
Error(traceback.format_exc())
Error("Exception: " + str(e))
sys.exit(1)