Merge lp:~louis/ubuntu/precise/walinuxagent/walinuxagent-lp1079897 into lp:ubuntu/precise-proposed/walinuxagent

Proposed by Louis Bouchard
Status: Merged
Merge reported by: James Page
Merged at revision: not available
Proposed branch: lp:~louis/ubuntu/precise/walinuxagent/walinuxagent-lp1079897
Merge into: lp:ubuntu/precise-proposed/walinuxagent
Diff against target: 5628 lines (+269/-5041)
19 files modified
.pc/000_ubuntu_init_resolvconf.patch/waagent (+0/-2335)
.pc/001_ubuntu_agent_startup.patch/waagent (+0/-2395)
.pc/applied-patches (+0/-2)
Changelog (+12/-0)
debian/changelog (+34/-0)
debian/control (+10/-0)
debian/patches/000_resolv-conf.patch (+32/-0)
debian/patches/000_ubuntu_init_resolvconf.patch (+0/-153)
debian/patches/001-strip-init-d.patch (+36/-0)
debian/patches/001_ubuntu_agent_startup.patch (+0/-22)
debian/patches/series (+2/-2)
debian/postinst (+7/-2)
debian/preinst (+16/-0)
debian/prerm (+2/-3)
debian/rules (+3/-4)
debian/upstart (+24/-0)
debian/walinuxagent-data-saver.lintian-overrides (+10/-0)
debian/walinuxagent-data-saver.preinst (+16/-0)
waagent (+65/-123)
To merge this branch: bzr merge lp:~louis/ubuntu/precise/walinuxagent/walinuxagent-lp1079897
Reviewer Review Type Date Requested Status
Ubuntu Development Team Pending
Review via email: mp+136698@code.launchpad.net

Description of the change

Backport of the fix for LP: #1079897 from Raring

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed directory '.pc/000_ubuntu_init_resolvconf.patch'
=== removed file '.pc/000_ubuntu_init_resolvconf.patch/waagent'
--- .pc/000_ubuntu_init_resolvconf.patch/waagent 2012-06-22 09:10:22 +0000
+++ .pc/000_ubuntu_init_resolvconf.patch/waagent 1970-01-01 00:00:00 +0000
@@ -1,2335 +0,0 @@
1#!/usr/bin/python
2#
3# Windows Azure Linux Agent
4#
5# Copyright 2012 Microsoft Corporation
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# Requires Python 2.4+ and Openssl 1.0+
20#
21# Implements parts of RFC 2131, 1541, 1497 and
22# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
23# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
24#
25
26import array
27import base64
28import httplib
29import os
30import os.path
31import platform
32import pwd
33import re
34import shutil
35import socket
36import SocketServer
37import struct
38import sys
39import tempfile
40import textwrap
41import threading
42import time
43import traceback
44import xml.dom.minidom
45
46GuestAgentName = "WALinuxAgent"
47GuestAgentLongName = "Windows Azure Linux Agent"
48GuestAgentVersion = "rd_wala.120504-1323"
49ProtocolVersion = "2011-12-31"
50
51Config = None
52LinuxDistro = "UNKNOWN"
53Verbose = False
54WaAgent = None
55DiskActivated = False
56Openssl = "openssl"
57
58PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"]
59RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
60 "/etc/udev/rules.d/70-persistent-net.rules" ]
61VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
62EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
63LibDir = "/var/lib/waagent"
64
65# This lets us index into a string or an array of integers transparently.
66def Ord(a):
67 if type(a) == type("a"):
68 a = ord(a)
69 return a
70
71def IsWindows():
72 return (platform.uname()[0] == "Windows")
73
74def IsLinux():
75 return (platform.uname()[0] == "Linux")
76
77def DetectLinuxDistro():
78 global LinuxDistro
79 if os.path.isfile("/etc/redhat-release"):
80 LinuxDistro = "RedHat"
81 return True
82 if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"):
83 LinuxDistro = "Ubuntu"
84 return True
85 if os.path.isfile("/etc/debian_version"):
86 LinuxDistro = "Debian"
87 return True
88 if os.path.isfile("/etc/SuSE-release"):
89 LinuxDistro = "Suse"
90 return True
91 return False
92
93def IsRedHat():
94 return "RedHat" in LinuxDistro
95
96def IsUbuntu():
97 return "Ubuntu" in LinuxDistro
98
99def IsDebian():
100 return IsUbuntu() or "Debian" in LinuxDistro
101
102def IsSuse():
103 return "Suse" in LinuxDistro
104
105def UsesRpm():
106 return IsRedHat() or IsSuse()
107
108def UsesDpkg():
109 return IsDebian()
110
111def GetLastPathElement(path):
112 return path.rsplit('/', 1)[1]
113
114def GetFileContents(filepath):
115 file = None
116 try:
117 file = open(filepath)
118 except:
119 return None
120 if file == None:
121 return None
122 try:
123 return file.read()
124 finally:
125 file.close()
126
127def SetFileContents(filepath, contents):
128 file = open(filepath, "w")
129 try:
130 file.write(contents)
131 finally:
132 file.close()
133
134def AppendFileContents(filepath, contents):
135 file = open(filepath, "a")
136 try:
137 file.write(contents)
138 finally:
139 file.close()
140
141def ReplaceFileContentsAtomic(filepath, contents):
142 handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
143 try:
144 os.write(handle, contents)
145 finally:
146 os.close(handle)
147 try:
148 os.rename(temp, filepath)
149 return
150 except:
151 pass
152 os.remove(filepath)
153 os.rename(temp, filepath)
154
155def GetLineStartingWith(prefix, filepath):
156 for line in GetFileContents(filepath).split('\n'):
157 if line.startswith(prefix):
158 return line
159 return None
160
161def Run(a):
162 LogIfVerbose(a)
163 return os.system(a)
164
165def GetNodeTextData(a):
166 for b in a.childNodes:
167 if b.nodeType == b.TEXT_NODE:
168 return b.data
169
170def GetHome():
171 home = None
172 try:
173 home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
174 except:
175 pass
176 if (home == None) or (home.startswith("/") == False):
177 home = "/home"
178 return home
179
180def ChangeOwner(filepath, user):
181 p = None
182 try:
183 p = pwd.getpwnam(user)
184 except:
185 pass
186 if p != None:
187 os.chown(filepath, p[2], p[3])
188
189def CreateDir(dirpath, user, mode):
190 try:
191 os.makedirs(dirpath, mode)
192 except:
193 pass
194 ChangeOwner(dirpath, user)
195
196def CreateAccount(user, password, expiration, thumbprint):
197 if IsWindows():
198 Log("Skipping CreateAccount on Windows")
199 return None
200 userentry = None
201 try:
202 userentry = pwd.getpwnam(user)
203 except:
204 pass
205 uidmin = None
206 try:
207 uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
208 except:
209 pass
210 if uidmin == None:
211 uidmin = 100
212 if userentry != None and userentry[2] < uidmin:
213 Error("CreateAccount: " + user + " is a system user. Will not set password.")
214 return "Failed to set password for system user: " + user + " (0x06)."
215 if userentry == None:
216 command = "useradd -m " + user
217 if expiration != None:
218 command += " -e " + expiration.split('.')[0]
219 if Run(command):
220 Error("Failed to create user account: " + user)
221 return "Failed to create user account: " + user + " (0x07)."
222 else:
223 Log("CreateAccount: " + user + " already exists. Will update password.")
224 if password != None:
225 os.popen("chpasswd", "w").write(user + ":" + password + "\n")
226 try:
227 if password == None:
228 SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
229 else:
230 SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
231 os.chmod("/etc/sudoers.d/waagent", 0440)
232 except:
233 Error("CreateAccount: Failed to configure sudo access for user.")
234 return "Failed to configure sudo privileges (0x08)."
235 home = GetHome()
236 if thumbprint != None:
237 dir = home + "/" + user + "/.ssh"
238 CreateDir(dir, user, 0700)
239 pub = dir + "/id_rsa.pub"
240 prv = dir + "/id_rsa"
241 Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
242 SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
243 for f in [pub, prv]:
244 os.chmod(f, 0600)
245 ChangeOwner(f, user)
246 SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
247 ChangeOwner(dir + "/authorized_keys", user)
248 Log("Created user account: " + user)
249 return None
250
251def DeleteAccount(user):
252 if IsWindows():
253 Log("Skipping DeleteAccount on Windows")
254 return
255 userentry = None
256 try:
257 userentry = pwd.getpwnam(user)
258 except:
259 pass
260 if userentry == None:
261 Error("DeleteAccount: " + user + " not found.")
262 return
263 uidmin = None
264 try:
265 uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
266 except:
267 pass
268 if uidmin == None:
269 uidmin = 100
270 if userentry[2] < uidmin:
271 Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
272 return
273 Run("userdel -f -r " + user)
274 try:
275 os.remove("/etc/sudoers.d/waagent")
276 except:
277 pass
278 return
279
280def ReloadSshd():
281 name = None
282 if IsRedHat() or IsSuse():
283 name = "sshd"
284 if IsDebian():
285 name = "ssh"
286 if name == None:
287 return
288 if not Run("service " + name + " status | grep running"):
289 Run("service " + name + " reload")
290
291def IsInRangeInclusive(a, low, high):
292 return (a >= low and a <= high)
293
294def IsPrintable(ch):
295 return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
296
297def HexDump(buffer, size):
298 if size < 0:
299 size = len(buffer)
300 result = ""
301 for i in range(0, size):
302 if (i % 16) == 0:
303 result += "%06X: " % i
304 byte = struct.unpack("B", buffer[i])[0]
305 result += "%02X " % byte
306 if (i & 15) == 7:
307 result += " "
308 if ((i + 1) % 16) == 0 or (i + 1) == size:
309 j = i
310 while ((j + 1) % 16) != 0:
311 result += " "
312 if (j & 7) == 7:
313 result += " "
314 j += 1
315 result += " "
316 for j in range(i - (i % 16), i + 1):
317 byte = struct.unpack("B", buffer[j])[0]
318 k = '.'
319 if IsPrintable(byte):
320 k = chr(byte)
321 result += k
322 if (i + 1) != size:
323 result += "\n"
324 return result
325
326def ThrottleLog(counter):
327 # Log everything up to 10, every 10 up to 100, then every 100.
328 return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
329
330def Logger():
331 class T(object):
332 def __init__(self):
333 self.File = None
334
335 self = T()
336
337 def LogToFile(message):
338 FilePath = ["/var/log/waagent.log", "waagent.log"][IsWindows()]
339 if not os.path.isfile(FilePath) and self.File != None:
340 self.File.close()
341 self.File = None
342 if self.File == None:
343 self.File = open(FilePath, "a")
344 self.File.write(message + "\n")
345 self.File.flush()
346
347 def Log(message):
348 LogWithPrefix("", message)
349
350 def LogWithPrefix(prefix, message):
351 t = time.localtime()
352 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)
353 t += prefix
354 for line in message.split('\n'):
355 line = t + line
356 print(line)
357 LogToFile(line)
358
359 return Log, LogWithPrefix
360
361Log, LogWithPrefix = Logger()
362
363def NoLog(message):
364 pass
365
366def LogIfVerbose(message):
367 if Verbose == True:
368 Log(message)
369
370def LogWithPrefixIfVerbose(prefix, message):
371 if Verbose == True:
372 LogWithPrefix(prefix, message)
373
374def Warn(message):
375 LogWithPrefix("WARNING:", message)
376
377def Error(message):
378 LogWithPrefix("ERROR:", message)
379
380def ErrorWithPrefix(prefix, message):
381 LogWithPrefix("ERROR:" + prefix, message)
382
383def Linux_ioctl_GetIpv4Address(ifname):
384 import fcntl
385 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
386 return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
387
388def Linux_ioctl_GetInterfaceMac(ifname):
389 import fcntl
390 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
391 info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
392 return ''.join(['%02X' % Ord(char) for char in info[18:24]])
393
394def GetIpv4Address():
395 if IsLinux():
396 for ifname in PossibleEthernetInterfaces:
397 try:
398 return Linux_ioctl_GetIpv4Address(ifname)
399 except IOError, e:
400 pass
401 else:
402 try:
403 return socket.gethostbyname(socket.gethostname())
404 except Exception, e:
405 ErrorWithPrefix("GetIpv4Address:", str(e))
406 ErrorWithPrefix("GetIpv4Address:", traceback.format_exc())
407
408def HexStringToByteArray(a):
409 b = ""
410 for c in range(0, len(a) / 2):
411 b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
412 return b
413
414def GetMacAddress():
415 if IsWindows():
416 # Windows: Physical Address. . . . . . . . . : 00-15-17-79-00-7F\n
417 a = "ipconfig /all | findstr /c:\"Physical Address\" | findstr /v \"00-00-00-00-00-00-00\""
418 a = os.popen(a).read()
419 a = re.sub("\s+$", "", a)
420 a = re.sub(".+ ", "", a)
421 a = re.sub(":", "", a)
422 a = re.sub("-", "", a)
423 else:
424 for ifname in PossibleEthernetInterfaces:
425 try:
426 a = Linux_ioctl_GetInterfaceMac(ifname)
427 break
428 except IOError, e:
429 pass
430 return HexStringToByteArray(a)
431
432def DeviceForIdePort(n):
433 if n > 3:
434 return None
435 g0 = "00000000"
436 if n > 1:
437 g0 = "00000001"
438 n = n - 2
439 device = None
440 path="/sys/bus/vmbus/devices/"
441 for vmbus in os.listdir(path):
442 guid=GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
443 if guid[0] == g0 and guid[1] == "000" + str(n):
444 for root, dirs, files in os.walk(path + vmbus):
445 if root.endswith("/block"):
446 device = dirs[0]
447 break
448 break
449 return device
450
451class Util(object):
452 def _HttpGet(self, url, headers):
453 LogIfVerbose("HttpGet(" + url + ")")
454 maxRetry = 2
455 if url.startswith("http://"):
456 url = url[7:]
457 url = url[url.index("/"):]
458 for retry in range(0, maxRetry + 1):
459 strRetry = str(retry)
460 log = [NoLog, Log][retry > 0]
461 log("retry HttpGet(" + url + "),retry=" + strRetry)
462 response = None
463 strStatus = "None"
464 try:
465 httpConnection = httplib.HTTPConnection(self.Endpoint)
466 if headers == None:
467 request = httpConnection.request("GET", url)
468 else:
469 request = httpConnection.request("GET", url, None, headers)
470 response = httpConnection.getresponse()
471 strStatus = str(response.status)
472 except:
473 pass
474 log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
475 if response == None or response.status != httplib.OK:
476 Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
477 if retry == maxRetry:
478 Log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
479 return None
480 else:
481 Log("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
482 time.sleep(10)
483 else:
484 log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
485 return response.read()
486
487 def HttpGetWithoutHeaders(self, url):
488 return self._HttpGet(url, None)
489
490 def HttpGetWithHeaders(self, url):
491 return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion})
492
493 def HttpSecureGetWithHeaders(self, url, transportCert):
494 return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName,
495 "x-ms-version": ProtocolVersion,
496 "x-ms-cipher-name": "DES_EDE3_CBC",
497 "x-ms-guest-agent-public-x509-cert": transportCert})
498
499 def HttpPost(self, url, data):
500 LogIfVerbose("HttpPost(" + url + ")")
501 maxRetry = 2
502 for retry in range(0, maxRetry + 1):
503 strRetry = str(retry)
504 log = [NoLog, Log][retry > 0]
505 log("retry HttpPost(" + url + "),retry=" + strRetry)
506 response = None
507 strStatus = "None"
508 try:
509 httpConnection = httplib.HTTPConnection(self.Endpoint)
510 request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName,
511 "Content-Type": "text/xml; charset=utf-8",
512 "x-ms-version": ProtocolVersion})
513 response = httpConnection.getresponse()
514 strStatus = str(response.status)
515 except:
516 pass
517 log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
518 if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED):
519 Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
520 if retry == maxRetry:
521 Log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
522 return None
523 else:
524 Log("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
525 time.sleep(10)
526 else:
527 log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
528 return response
529
530def LoadBalancerProbeServer(port):
531
532 class T(object):
533 def __init__(self, port):
534 enabled = Config.get("LBProbeResponder")
535 if enabled != None and enabled.lower().startswith("n"):
536 return
537 self.ProbeCounter = 0
538 self.server = SocketServer.TCPServer((GetIpv4Address(), port), TCPHandler)
539 self.server_thread = threading.Thread(target = self.server.serve_forever)
540 self.server_thread.setDaemon(True)
541 self.server_thread.start()
542
543 def shutdown(self):
544 global EnableLoadBalancerProbes
545 if not EnableLoadBalancerProbes:
546 return
547 self.server.shutdown()
548
549 class TCPHandler(SocketServer.BaseRequestHandler):
550 def GetHttpDateTimeNow(self):
551 # Date: Fri, 25 Mar 2011 04:53:10 GMT
552 return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
553
554 def handle(self):
555 context.ProbeCounter = (context.ProbeCounter + 1) % 1000000
556 log = [NoLog, LogIfVerbose][ThrottleLog(context.ProbeCounter)]
557 strCounter = str(context.ProbeCounter)
558 if context.ProbeCounter == 1:
559 Log("Receiving LB probes.")
560 log("Received LB probe # " + strCounter)
561 self.request.recv(1024)
562 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")
563
564 context = T(port)
565 return context
566
567class ConfigurationProvider(object):
568 def __init__(self):
569 self.values = dict()
570 if os.path.isfile("/etc/waagent.conf") == False:
571 raise Exception("Missing configuration in /etc/waagent.conf")
572 try:
573 for line in GetFileContents("/etc/waagent.conf").split('\n'):
574 if not line.startswith("#") and "=" in line:
575 parts = line.split()[0].split('=')
576 value = parts[1].strip("\" ")
577 if value != "None":
578 self.values[parts[0]] = value
579 else:
580 self.values[parts[0]] = None
581 except:
582 Error("Unable to parse /etc/waagent.conf")
583 raise
584 return
585
586 def get(self, key):
587 return self.values.get(key)
588
589class EnvMonitor(object):
590 def __init__(self):
591 self.shutdown = False
592 self.HostName = socket.gethostname()
593 self.server_thread = threading.Thread(target = self.monitor)
594 self.server_thread.setDaemon(True)
595 self.server_thread.start()
596 self.published = False
597
598 def monitor(self):
599 publish = Config.get("Provisioning.MonitorHostName")
600 dhcpcmd = "pidof dhclient"
601 if IsSuse():
602 dhcpcmd = "pidof dhcpcd"
603 if IsDebian():
604 dhcpcmd = "pidof dhclient3"
605 dhcppid = os.popen(dhcpcmd).read()
606 while not self.shutdown:
607 for a in RulesFiles:
608 if os.path.isfile(a):
609 if os.path.isfile(GetLastPathElement(a)):
610 os.remove(GetLastPathElement(a))
611 shutil.move(a, ".")
612 Log("EnvMonitor: Moved " + a + " -> " + LibDir)
613 if publish != None and publish.lower().startswith("y"):
614 try:
615 if socket.gethostname() != self.HostName:
616 Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
617 self.HostName = socket.gethostname()
618 WaAgent.UpdateAndPublishHostName(self.HostName)
619 dhcppid = os.popen(dhcpcmd).read()
620 self.published = True
621 except:
622 pass
623 else:
624 self.published = True
625 pid = ""
626 if not os.path.isdir("/proc/" + dhcppid.strip()):
627 pid = os.popen(dhcpcmd).read()
628 if pid != "" and pid != dhcppid:
629 Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
630 WaAgent.RestoreRoutes()
631 dhcppid = pid
632 time.sleep(5)
633
634 def SetHostName(self, name):
635 if socket.gethostname() == name:
636 self.published = True
637 else:
638 Run("hostname " + name)
639
640 def IsNamePublished(self):
641 return self.published
642
643 def shutdown(self):
644 self.shutdown = True
645 self.server_thread.join()
646
647class Certificates(object):
648#
649# <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
650# <Version>2010-12-15</Version>
651# <Incarnation>2</Incarnation>
652# <Format>Pkcs7BlobWithPfxContents</Format>
653# <Data>MIILTAY...
654# </Data>
655# </CertificateFile>
656#
657 def __init__(self):
658 self.reinitialize()
659
660 def reinitialize(self):
661 self.Incarnation = None
662 self.Role = None
663
664 def Parse(self, xmlText):
665 self.reinitialize()
666 SetFileContents("Certificates.xml", xmlText)
667 dom = xml.dom.minidom.parseString(xmlText)
668 for a in [ "CertificateFile", "Version", "Incarnation",
669 "Format", "Data", ]:
670 if not dom.getElementsByTagName(a):
671 Error("Certificates.Parse: Missing " + a)
672 return None
673 node = dom.childNodes[0]
674 if node.localName != "CertificateFile":
675 Error("Certificates.Parse: root not CertificateFile")
676 return None
677 SetFileContents("Certificates.p7m",
678 "MIME-Version: 1.0\n"
679 + "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
680 + "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
681 + "Content-Transfer-Encoding: base64\n\n"
682 + GetNodeTextData(dom.getElementsByTagName("Data")[0]))
683 if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
684 Error("Certificates.Parse: Failed to extract certificates from CMS message.")
685 return self
686 # There may be multiple certificates in this package. Split them.
687 file = open("Certificates.pem")
688 pindex = 1
689 cindex = 1
690 output = open("temp.pem", "w")
691 for line in file.readlines():
692 output.write(line)
693 if line.startswith("-----END PRIVATE KEY-----") or line.startswith("-----END CERTIFICATE-----"):
694 output.close()
695 if line.startswith("-----END PRIVATE KEY-----"):
696 os.rename("temp.pem", str(pindex) + ".prv")
697 pindex += 1
698 else:
699 os.rename("temp.pem", str(cindex) + ".crt")
700 cindex += 1
701 output = open("temp.pem", "w")
702 output.close()
703 os.remove("temp.pem")
704 keys = dict()
705 index = 1
706 filename = str(index) + ".crt"
707 while os.path.isfile(filename):
708 thumbprint = os.popen(Openssl + " x509 -in " + filename + " -fingerprint -noout").read().rstrip().split('=')[1].replace(':', '').upper()
709 pubkey=os.popen(Openssl + " x509 -in " + filename + " -pubkey -noout").read()
710 keys[pubkey] = thumbprint
711 os.rename(filename, thumbprint + ".crt")
712 os.chmod(thumbprint + ".crt", 0600)
713 if IsRedHat():
714 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + thumbprint + ".crt")
715 index += 1
716 filename = str(index) + ".crt"
717 index = 1
718 filename = str(index) + ".prv"
719 while os.path.isfile(filename):
720 pubkey = os.popen(Openssl + " rsa -in " + filename + " -pubout").read()
721 os.rename(filename, keys[pubkey] + ".prv")
722 os.chmod(keys[pubkey] + ".prv", 0600)
723 if IsRedHat():
724 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + keys[pubkey] + ".prv")
725 index += 1
726 filename = str(index) + ".prv"
727 return self
728
729class SharedConfig(object):
730#
731# <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
732# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
733# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
734# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
735# </Deployment>
736# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
737# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
738# <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
739# <Probes>
740# <Probe name="MachineRole" />
741# <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
742# <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
743# </Probes>
744# </LoadBalancerSettings>
745# <OutputEndpoints>
746# <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
747# <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
748# </Endpoint>
749# </OutputEndpoints>
750# <Instances>
751# <Instance id="MachineRole_IN_0" address="10.115.153.75">
752# <FaultDomains randomId="0" updateId="0" updateCount="0" />
753# <InputEndpoints>
754# <Endpoint name="a" address="10.115.153.75:80" protocol="http" isPublic="true" loadBalancedPublicAddress="70.37.106.197:80" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
755# <LocalPorts>
756# <LocalPortRange from="80" to="80" />
757# </LocalPorts>
758# </Endpoint>
759# <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
760# <LocalPorts>
761# <LocalPortRange from="3389" to="3389" />
762# </LocalPorts>
763# </Endpoint>
764# <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" address="10.115.153.75:20000" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.106.197:3389" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
765# <LocalPorts>
766# <LocalPortRange from="20000" to="20000" />
767# </LocalPorts>
768# </Endpoint>
769# </InputEndpoints>
770# </Instance>
771# </Instances>
772# </SharedConfig>
773#
774 def __init__(self):
775 self.reinitialize()
776
777 def reinitialize(self):
778 self.Deployment = None
779 self.Incarnation = None
780 self.Role = None
781 self.LoadBalancerSettings = None
782 self.OutputEndpoints = None
783 self.Instances = None
784
785 def Parse(self, xmlText):
786 self.reinitialize()
787 SetFileContents("SharedConfig.xml", xmlText)
788 dom = xml.dom.minidom.parseString(xmlText)
789 for a in [ "SharedConfig", "Deployment", "Service",
790 "ServiceInstance", "Incarnation", "Role", ]:
791 if not dom.getElementsByTagName(a):
792 Error("SharedConfig.Parse: Missing " + a)
793 return None
794 node = dom.childNodes[0]
795 if node.localName != "SharedConfig":
796 Error("SharedConfig.Parse: root not SharedConfig")
797 return None
798 program = Config.get("Role.TopologyConsumer")
799 if program != None:
800 os.spawnl(os.P_NOWAIT, program, program, LibDir + "/SharedConfig.xml")
801 return self
802
803class HostingEnvironmentConfig(object):
804#
805# <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
806# <StoredCertificates>
807# <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
808# </StoredCertificates>
809# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
810# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
811# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
812# </Deployment>
813# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
814# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
815# <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip">
816# <CAS mode="full" />
817# <PrivilegeLevel mode="max" />
818# <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties>
819# </HostingEnvironmentSettings>
820# <ApplicationSettings>
821# <Setting name="__ModelData" value="&lt;m role=&quot;MachineRole&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;MachineRole&quot;>&lt;e name=&quot;a&quot; />&lt;e name=&quot;b&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;/r>&lt;/m>" />
822# <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." />
823# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcN..." />
824# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2022-07-23T23:59:59.0000000-07:00" />
825# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test" />
826# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" />
827# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
828# <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" />
829# </ApplicationSettings>
830# <ResourceReferences>
831# <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" />
832# </ResourceReferences>
833# </HostingEnvironmentConfig>
834#
835 def __init__(self):
836 self.reinitialize()
837
838 def reinitialize(self):
839 self.StoredCertificates = None
840 self.Deployment = None
841 self.Incarnation = None
842 self.Role = None
843 self.HostingEnvironmentSettings = None
844 self.ApplicationSettings = None
845 self.Certificates = None
846 self.ResourceReferences = None
847
848 def Parse(self, xmlText):
849 self.reinitialize()
850 SetFileContents("HostingEnvironmentConfig.xml", xmlText)
851 dom = xml.dom.minidom.parseString(xmlText)
852 for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
853 "ServiceInstance", "Incarnation", "Role", ]:
854 if not dom.getElementsByTagName(a):
855 Error("HostingEnvironmentConfig.Parse: Missing " + a)
856 return None
857 node = dom.childNodes[0]
858 if node.localName != "HostingEnvironmentConfig":
859 Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
860 return None
861 self.ApplicationSettings = dom.getElementsByTagName("Setting")
862 self.Certificates = dom.getElementsByTagName("StoredCertificate")
863 return self
864
865 def DecryptPassword(self, e):
866 SetFileContents("password.p7m",
867 "MIME-Version: 1.0\n"
868 + "Content-Disposition: attachment; filename=\"password.p7m\"\n"
869 + "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
870 + "Content-Transfer-Encoding: base64\n\n"
871 + textwrap.fill(e, 64))
872 return os.popen(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem").read()
873
874 def ActivateResourceDisk(self):
875 global DiskActivated
876 if IsWindows():
877 DiskActivated = True
878 Log("Skipping ActivateResourceDisk on Windows")
879 return
880 format = Config.get("ResourceDisk.Format")
881 if format == None or format.lower().startswith("n"):
882 DiskActivated = True
883 return
884 device = DeviceForIdePort(1)
885 if device == None:
886 Error("ActivateResourceDisk: Unable to detect disk topology.")
887 return
888 device = "/dev/" + device
889 for entry in os.popen("mount").read().split():
890 if entry.startswith(device + "1"):
891 Log("ActivateResourceDisk: " + device + "1 is already mounted.")
892 DiskActivated = True
893 return
894 mountpoint = Config.get("ResourceDisk.MountPoint")
895 if mountpoint == None:
896 mountpoint = "/mnt/resource"
897 CreateDir(mountpoint, "root", 0755)
898 fs = Config.get("ResourceDisk.Filesystem")
899 if fs == None:
900 fs = "ext3"
901 if os.popen("sfdisk -q -c " + device + " 1").read().rstrip() == "7" and fs != "ntfs":
902 Run("sfdisk -c " + device + " 1 83")
903 Run("mkfs." + fs + " " + device + "1")
904 if Run("mount " + device + "1 " + mountpoint):
905 Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).")
906 return
907 Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs)
908 DiskActivated = True
909 swap = Config.get("ResourceDisk.EnableSwap")
910 if swap == None or swap.lower().startswith("n"):
911 return
912 sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
913 if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
914 os.remove(mountpoint + "/swapfile")
915 if not os.path.isfile(mountpoint + "/swapfile"):
916 Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
917 Run("mkswap " + mountpoint + "/swapfile")
918 if not Run("swapon " + mountpoint + "/swapfile"):
919 Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
920 else:
921 Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
922
923 def Process(self):
924 if DiskActivated == False:
925 diskThread = threading.Thread(target = self.ActivateResourceDisk)
926 diskThread.start()
927 User = None
928 Pass = None
929 Expiration = None
930 Thumbprint = None
931 for b in self.ApplicationSettings:
932 sname = b.getAttribute("name")
933 svalue = b.getAttribute("value")
934 if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword":
935 Pass = self.DecryptPassword(svalue)
936 elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername":
937 User = svalue
938 elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration":
939 Expiration = svalue
940 elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption":
941 Thumbprint = svalue.split(':')[1].upper()
942 if User != None and Pass != None:
943 if User != "root" and User != "" and Pass != "":
944 CreateAccount(User, Pass, Expiration, Thumbprint)
945 else:
946 Error("Not creating user account: user=" + User + " pass=" + Pass)
947 for c in self.Certificates:
948 cname = c.getAttribute("name")
949 csha1 = c.getAttribute("certificateId").split(':')[1].upper()
950 cpath = c.getAttribute("storeName")
951 clevel = c.getAttribute("configurationLevel")
952 if os.path.isfile(csha1 + ".prv"):
953 Log("Private key with thumbprint: " + csha1 + " was retrieved.")
954 if os.path.isfile(csha1 + ".crt"):
955 Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
956 program = Config.get("Role.ConfigurationConsumer")
957 if program != None:
958 os.spawnl(os.P_NOWAIT, program, program, LibDir + "/HostingEnvironmentConfig.xml")
959
960class GoalState(Util):
961#
962# <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
963# <Version>2010-12-15</Version>
964# <Incarnation>1</Incarnation>
965# <Machine>
966# <ExpectedState>Started</ExpectedState>
967# <LBProbePorts>
968# <Port>16001</Port>
969# </LBProbePorts>
970# </Machine>
971# <Container>
972# <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
973# <RoleInstanceList>
974# <RoleInstance>
975# <InstanceId>MachineRole_IN_0</InstanceId>
976# <State>Started</State>
977# <Configuration>
978# <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=hostingEnvironmentConfig&amp;incarnation=1</HostingEnvironmentConfig>
979# <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=sharedConfig&amp;incarnation=1</SharedConfig>
980# <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&amp;incarnation=1</Certificates>
981# </Configuration>
982# </RoleInstance>
983# </RoleInstanceList>
984# </Container>
985# </GoalState>
986#
987# There is only one Role for VM images.
988#
989# Of primary interest is:
990# Machine/ExpectedState -- this is how shutdown is requested
991# LBProbePorts -- an http server needs to run here
992# We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
993# And of course, Incarnation
994#
995 def __init__(self, Agent):
996 self.Agent = Agent
997 self.Endpoint = Agent.Endpoint
998 self.TransportCert = Agent.TransportCert
999 self.reinitialize()
1000
1001 def reinitialize(self):
1002 self.Incarnation = None # integer
1003 self.ExpectedState = None # "Started" or "Stopped"
1004 self.HostingEnvironmentConfigUrl = None
1005 self.HostingEnvironmentConfigXml = None
1006 self.HostingEnvironmentConfig = None
1007 self.SharedConfigUrl = None
1008 self.SharedConfigXml = None
1009 self.SharedConfig = None
1010 self.CertificatesUrl = None
1011 self.CertificatesXml = None
1012 self.Certificates = None
1013 self.RoleInstanceId = None
1014 self.ContainerId = None
1015 self.LoadBalancerProbePort = None # integer, ?list of integers
1016
1017 def Parse(self, xmlText):
1018 self.reinitialize()
1019 node = xml.dom.minidom.parseString(xmlText).childNodes[0]
1020 if node.localName != "GoalState":
1021 Error("GoalState.Parse: root not GoalState")
1022 return None
1023 for a in node.childNodes:
1024 if a.nodeType == node.ELEMENT_NODE:
1025 if a.localName == "Incarnation":
1026 self.Incarnation = GetNodeTextData(a)
1027 elif a.localName == "Machine":
1028 for b in a.childNodes:
1029 if b.nodeType == node.ELEMENT_NODE:
1030 if b.localName == "ExpectedState":
1031 self.ExpectedState = GetNodeTextData(b)
1032 Log("ExpectedState: " + self.ExpectedState)
1033 elif b.localName == "LBProbePorts":
1034 for c in b.childNodes:
1035 if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
1036 self.LoadBalancerProbePort = int(GetNodeTextData(c))
1037 elif a.localName == "Container":
1038 for b in a.childNodes:
1039 if b.nodeType == node.ELEMENT_NODE:
1040 if b.localName == "ContainerId":
1041 self.ContainerId = GetNodeTextData(b)
1042 Log("ContainerId: " + self.ContainerId)
1043 elif b.localName == "RoleInstanceList":
1044 for c in b.childNodes:
1045 if c.localName == "RoleInstance":
1046 for d in c.childNodes:
1047 if d.nodeType == node.ELEMENT_NODE:
1048 if d.localName == "InstanceId":
1049 self.RoleInstanceId = GetNodeTextData(d)
1050 Log("RoleInstanceId: " + self.RoleInstanceId)
1051 elif d.localName == "State":
1052 pass
1053 elif d.localName == "Configuration":
1054 for e in d.childNodes:
1055 if e.nodeType == node.ELEMENT_NODE:
1056 if e.localName == "HostingEnvironmentConfig":
1057 self.HostingEnvironmentConfigUrl = GetNodeTextData(e)
1058 LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl)
1059 self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
1060 self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
1061 elif e.localName == "SharedConfig":
1062 self.SharedConfigUrl = GetNodeTextData(e)
1063 LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
1064 self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
1065 self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
1066 elif e.localName == "Certificates":
1067 self.CertificatesUrl = GetNodeTextData(e)
1068 LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl)
1069 self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
1070 self.Certificates = Certificates().Parse(self.CertificatesXml)
1071 if self.Incarnation == None:
1072 Error("GoalState.Parse: Incarnation missing")
1073 return None
1074 if self.ExpectedState == None:
1075 Error("GoalState.Parse: ExpectedState missing")
1076 return None
1077 if self.RoleInstanceId == None:
1078 Error("GoalState.Parse: RoleInstanceId missing")
1079 return None
1080 if self.ContainerId == None:
1081 Error("GoalState.Parse: ContainerId missing")
1082 return None
1083 SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
1084 return self
1085
1086 def Process(self):
1087 self.HostingEnvironmentConfig.Process()
1088
1089class OvfEnv(object):
1090#
1091# <?xml version="1.0" encoding="utf-8"?>
1092# <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1093# <wa:ProvisioningSection>
1094# <wa:Version>1.0</wa:Version>
1095# <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
1096# <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
1097# <HostName>HostName</HostName>
1098# <UserName>UserName</UserName>
1099# <UserPassword>UserPassword</UserPassword>
1100# <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
1101# <SSH>
1102# <PublicKeys>
1103# <PublicKey>
1104# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
1105# <Path>$HOME/UserName/.ssh/authorized_keys</Path>
1106# </PublicKey>
1107# </PublicKeys>
1108# <KeyPairs>
1109# <KeyPair>
1110# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
1111# <Path>$HOME/UserName/.ssh/id_rsa</Path>
1112# </KeyPair>
1113# </KeyPairs>
1114# </SSH>
1115# </LinuxProvisioningConfigurationSet>
1116# </wa:ProvisioningSection>
1117# </Environment>
1118#
1119 def __init__(self):
1120 self.reinitialize()
1121
1122 def reinitialize(self):
1123 self.WaNs = "http://schemas.microsoft.com/windowsazure"
1124 self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
1125 self.MajorVersion = 1
1126 self.MinorVersion = 0
1127 self.ComputerName = None
1128 self.AdminPassword = None
1129 self.UserName = None
1130 self.UserPassword = None
1131 self.DisableSshPasswordAuthentication = True
1132 self.SshPublicKeys = []
1133 self.SshKeyPairs = []
1134
1135 def Parse(self, xmlText):
1136 self.reinitialize()
1137 dom = xml.dom.minidom.parseString(xmlText)
1138 if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
1139 Error("Unable to parse OVF XML.")
1140 section = None
1141 newer = False
1142 for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
1143 for n in p.childNodes:
1144 if n.localName == "Version":
1145 verparts = GetNodeTextData(n).split('.')
1146 major = int(verparts[0])
1147 minor = int(verparts[1])
1148 if major > self.MajorVersion:
1149 newer = True
1150 if major != self.MajorVersion:
1151 break
1152 if minor > self.MinorVersion:
1153 newer = True
1154 section = p
1155 if newer == True:
1156 Warn("Newer provisioning configuration detected. Please consider updating waagent.")
1157 if section == None:
1158 Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
1159 return None
1160 self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
1161 self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
1162 try:
1163 self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
1164 except:
1165 pass
1166 disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
1167 if len(disableSshPass) != 0:
1168 self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true")
1169 for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"):
1170 fp = None
1171 path = None
1172 for c in pkey.childNodes:
1173 if c.localName == "Fingerprint":
1174 fp = GetNodeTextData(c).upper()
1175 if c.localName == "Path":
1176 path = GetNodeTextData(c)
1177 self.SshPublicKeys += [[fp, path]]
1178 for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
1179 fp = None
1180 path = None
1181 for c in keyp.childNodes:
1182 if c.localName == "Fingerprint":
1183 fp = GetNodeTextData(c).upper()
1184 if c.localName == "Path":
1185 path = GetNodeTextData(c)
1186 self.SshKeyPairs += [[fp, path]]
1187 return self
1188
1189 def PrepareDir(self, filepath):
1190 home = GetHome()
1191 # Expand HOME variable if present in path
1192 path = os.path.normpath(filepath.replace("$HOME", home))
1193 if (path.startswith("/") == False) or (path.endswith("/") == True):
1194 return None
1195 dir = path.rsplit('/', 1)[0]
1196 if dir != "":
1197 CreateDir(dir, "root", 0700)
1198 if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
1199 ChangeOwner(dir, self.UserName)
1200 return path
1201
1202 def NumberToBytes(self, i):
1203 result = []
1204 while i:
1205 result.append(chr(i&0xFF))
1206 i >>= 8
1207 result.reverse()
1208 return ''.join(result)
1209
1210 def BitsToString(self, a):
1211 index=7
1212 s = ""
1213 c = 0
1214 for bit in a:
1215 c = c | (bit << index)
1216 index = index - 1
1217 if index == -1:
1218 s = s + struct.pack('>B', c)
1219 c = 0
1220 index = 7
1221 return s
1222
1223 def OpensslToSsh(self, file):
1224 from pyasn1.codec.der import decoder as der_decoder
1225 try:
1226 f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
1227 k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
1228 n=k[0]
1229 e=k[1]
1230 keydata=""
1231 keydata += struct.pack('>I',len("ssh-rsa"))
1232 keydata += "ssh-rsa"
1233 keydata += struct.pack('>I',len(self.NumberToBytes(e)))
1234 keydata += self.NumberToBytes(e)
1235 keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
1236 keydata += "\0"
1237 keydata += self.NumberToBytes(n)
1238 except Exception, e:
1239 print("OpensslToSsh: Exception " + str(e))
1240 return None
1241 return "ssh-rsa " + base64.b64encode(keydata) + "\n"
1242
1243 def Process(self):
1244 error = None
1245 WaAgent.EnvMonitor.SetHostName(self.ComputerName)
1246 if self.DisableSshPasswordAuthentication:
1247 filepath = "/etc/ssh/sshd_config"
1248 # Disable RFC 4252 and RFC 4256 authentication schemes.
1249 ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
1250 (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
1251 GetFileContents(filepath).split('\n'))) + "PasswordAuthentication no\nChallengeResponseAuthentication no\n")
1252 Log("Disabled SSH password-based authentication methods.")
1253 if self.AdminPassword != None:
1254 os.popen("chpasswd", "w").write("root:" + self.AdminPassword + "\n")
1255 if self.UserName != None:
1256 error = CreateAccount(self.UserName, self.UserPassword, None, None)
1257 sel = os.popen("getenforce").read().startswith("Enforcing")
1258 if sel == True and IsRedHat():
1259 Run("setenforce 0")
1260 home = GetHome()
1261 for pkey in self.SshPublicKeys:
1262 if not os.path.isfile(pkey[0] + ".crt"):
1263 Error("PublicKey not found: " + pkey[0])
1264 error = "Failed to deploy public key (0x09)."
1265 continue
1266 path = self.PrepareDir(pkey[1])
1267 if path == None:
1268 Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
1269 error = "Invalid path for public key (0x03)."
1270 continue
1271 Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
1272 if IsRedHat():
1273 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + pkey[0] + ".pub")
1274 if IsUbuntu():
1275 # Only supported in new SSH releases
1276 Run("ssh-keygen -i -m PKCS8 -f " + pkey[0] + ".pub >> " + path)
1277 else:
1278 SshPubKey = self.OpensslToSsh(pkey[0] + ".pub")
1279 if SshPubKey != None:
1280 AppendFileContents(path, SshPubKey)
1281 else:
1282 Error("Failed: " + pkey[0] + ".crt -> " + path)
1283 error = "Failed to deploy public key (0x04)."
1284 if IsRedHat():
1285 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
1286 if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
1287 ChangeOwner(path, self.UserName)
1288 for keyp in self.SshKeyPairs:
1289 if not os.path.isfile(keyp[0] + ".prv"):
1290 Error("KeyPair not found: " + keyp[0])
1291 error = "Failed to deploy key pair (0x0A)."
1292 continue
1293 path = self.PrepareDir(keyp[1])
1294 if path == None:
1295 Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
1296 error = "Invalid path for key pair (0x05)."
1297 continue
1298 SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
1299 os.chmod(path, 0600)
1300 Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
1301 if IsRedHat():
1302 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
1303 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path + ".pub")
1304 if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
1305 ChangeOwner(path, self.UserName)
1306 ChangeOwner(path + ".pub", self.UserName)
1307 if sel == True and IsRedHat():
1308 Run("setenforce 1")
1309 while not WaAgent.EnvMonitor.IsNamePublished():
1310 time.sleep(1)
1311 ReloadSshd()
1312 return error
1313
1314def UpdateAndPublishHostNameCommon(name):
1315 # RedHat
1316 if IsRedHat():
1317 filepath = "/etc/sysconfig/network"
1318 if os.path.isfile(filepath):
1319 ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n"
1320 + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n'))))
1321
1322 for ethernetInterface in PossibleEthernetInterfaces:
1323 filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
1324 if os.path.isfile(filepath):
1325 ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
1326 + "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
1327
1328 # Debian
1329 if IsDebian():
1330 SetFileContents("/etc/hostname", name)
1331
1332 for filepath in EtcDhcpClientConfFiles:
1333 if os.path.isfile(filepath):
1334 ReplaceFileContentsAtomic(filepath, "send host-name \"" + name + "\";\n"
1335 + "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents(filepath).split('\n'))))
1336
1337 # Suse
1338 if IsSuse():
1339 SetFileContents("/etc/HOSTNAME", name)
1340
1341class Agent(Util):
1342 def __init__(self):
1343 self.GoalState = None
1344 self.Endpoint = None
1345 self.LoadBalancerProbeServer = None
1346 self.HealthReportCounter = 0
1347 self.TransportCert = ""
1348 self.EnvMonitor = None
1349 self.SendData = None
1350 self.DhcpResponse = None
1351
1352 def CheckVersions(self):
1353 #<?xml version="1.0" encoding="utf-8"?>
1354 #<Versions>
1355 # <Preferred>
1356 # <Version>2010-12-15</Version>
1357 # </Preferred>
1358 # <Supported>
1359 # <Version>2010-12-15</Version>
1360 # <Version>2010-28-10</Version>
1361 # </Supported>
1362 #</Versions>
1363 global ProtocolVersion
1364 protocolVersionSeen = False
1365 node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
1366 if node.localName != "Versions":
1367 Error("CheckVersions: root not Versions")
1368 return False
1369 for a in node.childNodes:
1370 if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
1371 for b in a.childNodes:
1372 if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
1373 v = GetNodeTextData(b)
1374 LogIfVerbose("Fabric supported wire protocol version: " + v)
1375 if v == ProtocolVersion:
1376 protocolVersionSeen = True
1377 if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
1378 v = GetNodeTextData(a.getElementsByTagName("Version")[0])
1379 LogIfVerbose("Fabric preferred wire protocol version: " + v)
1380 if ProtocolVersion < v:
1381 Warn("Newer wire protocol version detected. Please consider updating waagent.")
1382 if not protocolVersionSeen:
1383 Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
1384 ProtocolVersion = "2011-08-31"
1385 Log("Negotiated wire protocol version: " + ProtocolVersion)
1386 return True
1387
1388 def Unpack(self, buffer, offset, range):
1389 result = 0
1390 for i in range:
1391 result = (result << 8) | Ord(buffer[offset + i])
1392 return result
1393
1394 def UnpackLittleEndian(self, buffer, offset, length):
1395 return self.Unpack(buffer, offset, range(length - 1, -1, -1))
1396
1397 def UnpackBigEndian(self, buffer, offset, length):
1398 return self.Unpack(buffer, offset, range(0, length))
1399
1400 def HexDump3(self, buffer, offset, length):
1401 return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
1402
1403 def HexDump2(self, buffer):
1404 return self.HexDump3(buffer, 0, len(buffer))
1405
1406 def BuildDhcpRequest(self):
1407 #
1408 # typedef struct _DHCP {
1409 # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
1410 # UINT8 HardwareAddressType; /* htype: ethernet */
1411 # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
1412 # UINT8 Hops; /* hops: 0 */
1413 # UINT8 TransactionID[4]; /* xid: random */
1414 # UINT8 Seconds[2]; /* secs: 0 */
1415 # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
1416 # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
1417 # UINT8 YourIpAddress[4]; /* yiaddr: 0 */
1418 # UINT8 ServerIpAddress[4]; /* siaddr: 0 */
1419 # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
1420 # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */
1421 # UINT8 ServerName[64]; /* sname: 0 */
1422 # UINT8 BootFileName[128]; /* file: 0 */
1423 # UINT8 MagicCookie[4]; /* 99 130 83 99 */
1424 # /* 0x63 0x82 0x53 0x63 */
1425 # /* options -- hard code ours */
1426 #
1427 # UINT8 MessageTypeCode; /* 53 */
1428 # UINT8 MessageTypeLength; /* 1 */
1429 # UINT8 MessageType; /* 1 for DISCOVER */
1430 # UINT8 End; /* 255 */
1431 # } DHCP;
1432 #
1433
1434 # tuple of 244 zeros
1435 # (struct.pack_into would be good here, but requires Python 2.5)
1436 sendData = [0] * 244
1437
1438 transactionID = os.urandom(4)
1439 macAddress = GetMacAddress()
1440
1441 # Opcode = 1
1442 # HardwareAddressType = 1 (ethernet/MAC)
1443 # HardwareAddressLength = 6 (ethernet/MAC/48 bits)
1444 for a in range(0, 3):
1445 sendData[a] = [1, 1, 6][a]
1446
1447 # fill in transaction id (random number to ensure response matches request)
1448 for a in range(0, 4):
1449 sendData[4 + a] = Ord(transactionID[a])
1450
1451 LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
1452
1453 # fill in ClientHardwareAddress
1454 for a in range(0, 6):
1455 sendData[0x1C + a] = Ord(macAddress[a])
1456
1457 # DHCP Magic Cookie: 99, 130, 83, 99
1458 # MessageTypeCode = 53 DHCP Message Type
1459 # MessageTypeLength = 1
1460 # MessageType = DHCPDISCOVER
1461 # End = 255 DHCP_END
1462 for a in range(0, 8):
1463 sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
1464 return array.array("c", map(chr, sendData))
1465
1466 def IntegerToIpAddressV4String(self, a):
1467 return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
1468
1469 def RouteAdd(self, net, mask, gateway):
1470 if IsWindows():
1471 return
1472 net = self.IntegerToIpAddressV4String(net)
1473 mask = self.IntegerToIpAddressV4String(mask)
1474 gateway = self.IntegerToIpAddressV4String(gateway)
1475 Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway)
1476
1477 def HandleDhcpResponse(self, sendData, receiveBuffer):
1478 LogIfVerbose("HandleDhcpResponse")
1479 bytesReceived = len(receiveBuffer)
1480 if bytesReceived < 0xF6:
1481 Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
1482 return None
1483
1484 LogIfVerbose("BytesReceived: " + hex(bytesReceived))
1485 LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
1486
1487 # check transactionId, cookie, MAC address
1488 # cookie should never mismatch
1489 # transactionId and MAC address may mismatch if we see a response meant from another machine
1490
1491 for offsets in [range(4, 4 + 4), range(0x1C, 0x1C + 6), range(0xEC, 0xEC + 4)]:
1492 for offset in offsets:
1493 sentByte = Ord(sendData[offset])
1494 receivedByte = Ord(receiveBuffer[offset])
1495 if sentByte != receivedByte:
1496 LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
1497 LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
1498 LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
1499 LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
1500 LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
1501 LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
1502 LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
1503 return None
1504 endpoint = None
1505
1506 #
1507 # Walk all the returned options, parsing out what we need, ignoring the others.
1508 # We need the custom option 245 to find the the endpoint we talk to,
1509 # as well as, to handle some Linux DHCP client incompatibilities,
1510 # options 3 for default gateway and 249 for routes. And 255 is end.
1511 #
1512
1513 i = 0xF0 # offset to first option
1514 while i < bytesReceived:
1515 option = Ord(receiveBuffer[i])
1516 length = 0
1517 if (i + 1) < bytesReceived:
1518 length = Ord(receiveBuffer[i + 1])
1519 LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
1520 if option == 255:
1521 LogIfVerbose("DHCP packet ended at offset " + hex(i))
1522 break
1523 elif option == 249:
1524 # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
1525 LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
1526 if length < 5:
1527 Error("Data too small for option " + option)
1528 j = i + 2
1529 while j < (i + length + 2):
1530 maskLengthBits = Ord(receiveBuffer[j])
1531 maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
1532 mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
1533 j += 1
1534 net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
1535 net <<= (32 - maskLengthBytes * 8)
1536 net &= mask
1537 j += maskLengthBytes
1538 gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
1539 j += 4
1540 self.RouteAdd(net, mask, gateway)
1541 if j != (i + length + 2):
1542 Error("HandleDhcpResponse: Unable to parse routes")
1543 elif option == 3 or option == 245:
1544 if i + 5 < bytesReceived:
1545 if length != 4:
1546 Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
1547 return None
1548 gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
1549 IpAddress = self.IntegerToIpAddressV4String(gateway)
1550 if option == 3:
1551 self.RouteAdd(0, 0, gateway)
1552 name = "DefaultGateway"
1553 else:
1554 endpoint = IpAddress
1555 name = "Windows Azure wire protocol endpoint"
1556 LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
1557 else:
1558 Error("HandleDhcpResponse: Data too small for option " + option)
1559 else:
1560 LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
1561 i += length + 2
1562 return endpoint
1563
1564 def DoDhcpWork(self):
1565 #
1566 # Discover the wire server via DHCP option 245.
1567 # And workaround incompatibility with Windows Azure DHCP servers.
1568 #
1569 ShortSleep = False # Sleep 1 second before retrying DHCP queries.
1570
1571 if not IsWindows():
1572 Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT")
1573 Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT")
1574
1575 sleepDurations = [0, 5, 10, 30, 60, 60, 60, 60]
1576 maxRetry = len(sleepDurations)
1577 lastTry = (maxRetry - 1)
1578 for retry in range(0, maxRetry):
1579 try:
1580 strRetry = str(retry)
1581 prefix = "DoDhcpWork: try=" + strRetry
1582 LogIfVerbose(prefix)
1583 sendData = self.BuildDhcpRequest()
1584 LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
1585 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
1586 sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
1587 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1588 if IsSuse():
1589 # This is required because sending after binding to 0.0.0.0 fails with
1590 # network unreachable when the default gateway is not set up.
1591 sock.bind((GetIpv4Address(), 68))
1592 else:
1593 sock.bind(("0.0.0.0", 68))
1594 sock.sendto(sendData, ("<broadcast>", 67))
1595 receiveBuffer = sock.recv(1024)
1596 sock.close()
1597 endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
1598 if endpoint == None:
1599 LogIfVerbose("DoDhcpWork: No endpoint found")
1600 if endpoint != None or retry == lastTry:
1601 if endpoint != None:
1602 self.SendData = sendData
1603 self.DhcpResponse = receiveBuffer
1604 if retry == lastTry:
1605 LogIfVerbose("DoDhcpWork: try=" + strRetry)
1606 return endpoint
1607 sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
1608 LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
1609 time.sleep(sleepDuration)
1610 except Exception, e:
1611 ErrorWithPrefix(prefix, str(e))
1612 ErrorWithPrefix(prefix, traceback.format_exc())
1613 return None
1614
1615 def UpdateAndPublishHostName(self, name):
1616 # Set hostname locally and publish to iDNS
1617 Log("Setting host name: " + name)
1618 UpdateAndPublishHostNameCommon(name)
1619 for ethernetInterface in PossibleEthernetInterfaces:
1620 if IsSuse():
1621 Run("ifrenew " + ethernetInterface)
1622 else:
1623 Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface)
1624 self.RestoreRoutes()
1625
1626 def RestoreRoutes(self):
1627 if self.SendData != None and self.DhcpResponse != None:
1628 self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
1629
1630 def UpdateGoalState(self):
1631 goalStateXml = None
1632 maxRetry = 9
1633 log = NoLog
1634 for retry in range(1, maxRetry + 1):
1635 strRetry = str(retry)
1636 log("retry UpdateGoalState,retry=" + strRetry)
1637 goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
1638 if goalStateXml != None:
1639 break
1640 log = Log
1641 time.sleep(retry)
1642 if not goalStateXml:
1643 Error("UpdateGoalState failed.")
1644 return
1645 Log("Retrieved GoalState from Windows Azure Fabric.")
1646 self.GoalState = GoalState(self).Parse(goalStateXml)
1647 return self.GoalState
1648
1649 def ReportReady(self):
1650 counter = (self.HealthReportCounter + 1) % 1000000
1651 self.HealthReportCounter = counter
1652 healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
1653 + self.GoalState.Incarnation
1654 + "</GoalStateIncarnation><Container><ContainerId>"
1655 + self.GoalState.ContainerId
1656 + "</ContainerId><RoleInstanceList><Role><InstanceId>"
1657 + self.GoalState.RoleInstanceId
1658 + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>")
1659 a = self.HttpPost("/machine?comp=health", healthReport)
1660 if a != None:
1661 return a.getheader("x-ms-latest-goal-state-incarnation-number")
1662 return None
1663
1664 def ReportNotReady(self, status, desc):
1665 healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
1666 + self.GoalState.Incarnation
1667 + "</GoalStateIncarnation><Container><ContainerId>"
1668 + self.GoalState.ContainerId
1669 + "</ContainerId><RoleInstanceList><Role><InstanceId>"
1670 + self.GoalState.RoleInstanceId
1671 + "</InstanceId><Health><State>NotReady</State>"
1672 + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>"
1673 + "</Health></Role></RoleInstanceList></Container></Health>")
1674 a = self.HttpPost("/machine?comp=health", healthReport)
1675 if a != None:
1676 return a.getheader("x-ms-latest-goal-state-incarnation-number")
1677 return None
1678
1679 def ReportRoleProperties(self, thumbprint):
1680 roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>"
1681 + "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>"
1682 + "<RoleInstances><RoleInstance>"
1683 + "<Id>" + self.GoalState.RoleInstanceId + "</Id>"
1684 + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>"
1685 + "</RoleInstance></RoleInstances></Container></RoleProperties>")
1686 a = self.HttpPost("/machine?comp=roleProperties", roleProperties)
1687 Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
1688 return a
1689
1690 def LoadBalancerProbeServer_Shutdown(self):
1691 if self.LoadBalancerProbeServer != None:
1692 self.LoadBalancerProbeServer.shutdown()
1693 self.LoadBalancerProbeServer = None
1694
1695 def GenerateTransportCert(self):
1696 Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
1697 cert = ""
1698 for line in GetFileContents("TransportCert.pem").split('\n'):
1699 if not "CERTIFICATE" in line:
1700 cert += line.rstrip()
1701 return cert
1702
1703 def Provision(self):
1704 if IsWindows():
1705 Log("Skipping Provision on Windows")
1706 return
1707 enabled = Config.get("Provisioning.Enabled")
1708 if enabled != None and enabled.lower().startswith("n"):
1709 return
1710 Log("Provisioning image started.")
1711 type = Config.get("Provisioning.SshHostKeyPairType")
1712 if type == None:
1713 type = "rsa"
1714 regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
1715 if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
1716 Run("rm -f /etc/ssh/ssh_host_*key*")
1717 Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key")
1718 ReloadSshd()
1719 SetFileContents(LibDir + "/provisioned", "")
1720 dvd = "/dev/hdc"
1721 if os.path.exists("/dev/sr0"):
1722 dvd = "/dev/sr0"
1723 if Run("fdisk -l " + dvd + " | grep Disk"):
1724 return
1725 CreateDir("/mnt/cdrom/secure", "root", 0700)
1726 if Run("mount " + dvd + " /mnt/cdrom/secure"):
1727 Error("Unable to provision: Failed to mount DVD.")
1728 return "Failed to retrieve provisioning data (0x01)."
1729 if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
1730 Error("Unable to provision: Missing ovf-env.xml on DVD.")
1731 return "Failed to retrieve provisioning data (0x02)."
1732 ovfxml = GetFileContents("/mnt/cdrom/secure/ovf-env.xml")
1733 SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml))
1734 Run("umount /mnt/cdrom/secure")
1735 error = None
1736 if ovfxml != None:
1737 Log("Provisioning image using OVF settings in the DVD.")
1738 ovfobj = OvfEnv().Parse(ovfxml)
1739 if ovfobj != None:
1740 error = ovfobj.Process()
1741 # This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml
1742 fingerprint = os.popen("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub").read().rstrip().split()[1].replace(':','')
1743 self.ReportRoleProperties(fingerprint)
1744 delRootPass = Config.get("Provisioning.DeleteRootPassword")
1745 if delRootPass != None and delRootPass.lower().startswith("y"):
1746 DeleteRootPassword()
1747 Log("Provisioning image completed.")
1748 return error
1749
1750 def Run(self):
1751 if IsLinux():
1752 SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
1753
1754 if GetIpv4Address() == None:
1755 Log("Waiting for network.")
1756 while(GetIpv4Address() == None):
1757 time.sleep(10)
1758
1759 Log("IPv4 address: " + GetIpv4Address())
1760 Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in GetMacAddress()]))
1761
1762 # Consume Entropy in ACPI table provided by Hyper-V
1763 try:
1764 SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
1765 except:
1766 pass
1767
1768 Log("Probing for Windows Azure environment.")
1769 self.Endpoint = self.DoDhcpWork()
1770
1771 if self.Endpoint == None:
1772 Log("Windows Azure environment not detected.")
1773 while True:
1774 time.sleep(60)
1775
1776 Log("Discovered Windows Azure endpoint: " + self.Endpoint)
1777 if not self.CheckVersions():
1778 Error("Agent.CheckVersions failed")
1779 sys.exit(1)
1780
1781 self.EnvMonitor = EnvMonitor()
1782
1783 # Set SCSI timeout on root device
1784 try:
1785 timeout = Config.get("OS.RootDeviceScsiTimeout")
1786 if timeout != None:
1787 SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout)
1788 except:
1789 pass
1790
1791 global Openssl
1792 Openssl = Config.get("OS.OpensslPath")
1793 if Openssl == None:
1794 Openssl = "openssl"
1795
1796 self.TransportCert = self.GenerateTransportCert()
1797
1798 incarnation = None # goalStateIncarnationFromHealthReport
1799 currentPort = None # loadBalancerProbePort
1800 goalState = None # self.GoalState, instance of GoalState
1801 provisioned = os.path.exists(LibDir + "/provisioned")
1802 program = Config.get("Role.StateConsumer")
1803 provisionError = None
1804 while True:
1805 if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
1806 goalState = self.UpdateGoalState()
1807
1808 if provisioned == False:
1809 self.ReportNotReady("Provisioning", "Starting")
1810
1811 goalState.Process()
1812
1813 if provisioned == False:
1814 provisionError = self.Provision()
1815 provisioned = True
1816
1817 #
1818 # only one port supported
1819 # restart server if new port is different than old port
1820 # stop server if no longer a port
1821 #
1822 goalPort = goalState.LoadBalancerProbePort
1823 if currentPort != goalPort:
1824 self.LoadBalancerProbeServer_Shutdown()
1825 currentPort = goalPort
1826 if currentPort != None:
1827 self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)
1828
1829 if program != None and DiskActivated == True:
1830 os.spawnl(os.P_NOWAIT, program, program, "Ready")
1831 program = None
1832
1833 if goalState.ExpectedState == "Stopped":
1834 program = Config.get("Role.StateConsumer")
1835 if program != None:
1836 Run(program + " Shutdown")
1837 self.EnvMonitor.shutdown()
1838 self.LoadBalancerProbeServer_Shutdown()
1839 command = ["/sbin/shutdown -hP now", "shutdown /s /t 5"][IsWindows()]
1840 Run(command)
1841 return
1842
1843 sleepToReduceAccessDenied = 3
1844 time.sleep(sleepToReduceAccessDenied)
1845 i = None
1846 if provisionError != None:
1847 i = self.ReportNotReady("ProvisioningFailed", provisionError)
1848 else:
1849 i = self.ReportReady()
1850 if i != None:
1851 incarnation = i
1852 time.sleep(25 - sleepToReduceAccessDenied)
1853
1854Init_Suse = """\
1855#! /bin/sh
1856
1857### BEGIN INIT INFO
1858# Provides: WindowsAzureLinuxAgent
1859# Required-Start: $network sshd
1860# Required-Stop: $network sshd
1861# Default-Start: 3 5
1862# Default-Stop: 0 1 2 6
1863# Description: Start the WindowsAzureLinuxAgent
1864### END INIT INFO
1865
1866WAZD_BIN=/usr/sbin/waagent
1867test -x $WAZD_BIN || exit 5
1868
1869case "$1" in
1870 start)
1871 echo "Starting WindowsAzureLinuxAgent"
1872 ## Start daemon with startproc(8). If this fails
1873 ## the echo return value is set appropriate.
1874
1875 startproc -f $WAZD_BIN -daemon
1876 exit $?
1877 ;;
1878 stop)
1879 echo "Shutting down WindowsAzureLinuxAgent"
1880 ## Stop daemon with killproc(8) and if this fails
1881 ## set echo the echo return value.
1882
1883 killproc -p /var/run/waagent.pid $WAZD_BIN
1884 exit $?
1885 ;;
1886 try-restart)
1887 ## Stop the service and if this succeeds (i.e. the
1888 ## service was running before), start it again.
1889 $0 status >/dev/null && $0 restart
1890 ;;
1891 restart)
1892 ## Stop the service and regardless of whether it was
1893 ## running or not, start it again.
1894 $0 stop
1895 $0 start
1896 ;;
1897 force-reload|reload)
1898 ;;
1899 status)
1900 echo -n "Checking for service WindowsAzureLinuxAgent "
1901 ## Check status with checkproc(8), if process is running
1902 ## checkproc will return with exit status 0.
1903
1904 checkproc -p $WAZD_PIDFILE $WAZD_BIN
1905 exit $?
1906 ;;
1907 probe)
1908 ;;
1909 *)
1910 echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
1911 exit 1
1912 ;;
1913esac
1914"""
1915
1916Init_RedHat = """\
1917#!/bin/bash
1918#
1919# Init file for WindowsAzureLinuxAgent.
1920#
1921# chkconfig: 2345 60 80
1922# description: WindowsAzureLinuxAgent
1923#
1924
1925# source function library
1926. /etc/rc.d/init.d/functions
1927
1928RETVAL=0
1929FriendlyName="WindowsAzureLinuxAgent"
1930WAZD_BIN=/usr/sbin/waagent
1931
1932start()
1933{
1934 echo -n $"Starting $FriendlyName: "
1935 $WAZD_BIN -daemon &
1936}
1937
1938stop()
1939{
1940 echo -n $"Stopping $FriendlyName: "
1941 killproc -p /var/run/waagent.pid $WAZD_BIN
1942 RETVAL=$?
1943 echo
1944 return $RETVAL
1945}
1946
1947case "$1" in
1948 start)
1949 start
1950 ;;
1951 stop)
1952 stop
1953 ;;
1954 restart)
1955 stop
1956 start
1957 ;;
1958 reload)
1959 ;;
1960 report)
1961 ;;
1962 status)
1963 status $WAZD_BIN
1964 RETVAL=$?
1965 ;;
1966 *)
1967 echo $"Usage: $0 {start|stop|restart|status}"
1968 RETVAL=1
1969esac
1970exit $RETVAL
1971"""
1972
1973Init_Debian = """\
1974#!/bin/sh
1975### BEGIN INIT INFO
1976# Provides: WindowsAzureLinuxAgent
1977# Required-Start: $network $syslog
1978# Required-Stop: $network $syslog
1979# Should-Start: $network $syslog
1980# Should-Stop: $network $syslog
1981# Default-Start: 2 3 4 5
1982# Default-Stop: 0 1 6
1983# Short-Description: WindowsAzureLinuxAgent
1984# Description: WindowsAzureLinuxAgent
1985### END INIT INFO
1986
1987. /lib/lsb/init-functions
1988
1989OPTIONS="-daemon"
1990WAZD_BIN=/usr/sbin/waagent
1991WAZD_PID=/var/run/waagent.pid
1992
1993case "$1" in
1994 start)
1995 log_begin_msg "Starting WindowsAzureLinuxAgent..."
1996 pid=$( pidofproc $WAZD_BIN )
1997 if [ -n "$pid" ] ; then
1998 log_begin_msg "Already running."
1999 log_end_msg 0
2000 exit 0
2001 fi
2002 start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
2003 log_end_msg $?
2004 ;;
2005
2006 stop)
2007 log_begin_msg "Stopping WindowsAzureLinuxAgent..."
2008 start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
2009 ret=$?
2010 rm -f $WAZD_PID
2011 log_end_msg $ret
2012 ;;
2013 force-reload)
2014 $0 restart
2015 ;;
2016 restart)
2017 $0 stop
2018 $0 start
2019 ;;
2020 status)
2021 status_of_proc $WAZD_BIN && exit 0 || exit $?
2022 ;;
2023 *)
2024 log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
2025 exit 1
2026 ;;
2027esac
2028
2029exit 0
2030"""
2031
2032WaagentConf = """\
2033#
2034# Windows Azure Linux Agent Configuration
2035#
2036
2037Role.StateConsumer=None # Specified program is invoked with "Ready" or "Shutdown".
2038 # Shutdown will be initiated only after the program returns. Windows Azure will
2039 # power off the VM if shutdown is not completed within ?? minutes.
2040Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
2041Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
2042
2043Provisioning.Enabled=y #
2044Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
2045Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
2046Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
2047Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
2048
2049ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
2050ResourceDisk.Filesystem=ext4 #
2051ResourceDisk.MountPoint=/mnt/resource #
2052ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
2053ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
2054
2055LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
2056
2057Logs.Verbose=n #
2058
2059OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
2060OS.OpensslPath=None # If "None", the system default version is used.
2061"""
2062
2063WaagentLogrotate = """\
2064/var/log/waagent.log {
2065 monthly
2066 rotate 6
2067 notifempty
2068 missingok
2069}
2070"""
2071
2072def AddToLinuxKernelCmdline(options):
2073 if os.path.isfile("/boot/grub/menu.lst"):
2074 Run("sed -i --follow-symlinks '/kernel/s|$| " + options + " |' /boot/grub/menu.lst")
2075 filepath = "/etc/default/grub"
2076 if os.path.isfile(filepath):
2077 filecontents = GetFileContents(filepath).split('\n')
2078 current = filter(lambda a: a.startswith("GRUB_CMDLINE_LINUX"), filecontents)
2079 ReplaceFileContentsAtomic(filepath,
2080 "\n".join(filter(lambda a: not a.startswith("GRUB_CMDLINE_LINUX"), filecontents))
2081 + current[0][:-1] + " " + options + "\"\n")
2082 Run("update-grub")
2083
2084def ApplyVNUMAWorkaround():
2085 VersionParts = platform.release().replace('-', '.').split('.')
2086 if int(VersionParts[0]) > 2:
2087 return
2088 if int(VersionParts[1]) > 6:
2089 return
2090 if int(VersionParts[2]) > 37:
2091 return
2092 AddToLinuxKernelCmdline("numa=off")
2093 # TODO: This is not ideal for offline installation.
2094 print("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
2095
2096def RevertVNUMAWorkaround():
2097 print("Automatic reverting of GRUB configuration is not yet supported. Please edit by hand.")
2098
2099def Install():
2100 if IsWindows():
2101 print("ERROR: -install invalid for Windows.")
2102 return 1
2103 os.chmod(sys.argv[0], 0755)
2104 SwitchCwd()
2105 requiredDeps = [ "/sbin/route", "/sbin/shutdown" ]
2106 if IsDebian():
2107 requiredDeps += [ "/usr/sbin/update-rc.d" ]
2108 if IsSuse():
2109 requiredDeps += [ "/sbin/insserv" ]
2110 for a in requiredDeps:
2111 if not os.path.isfile(a):
2112 Error("Missing required dependency: " + a)
2113 return 1
2114 missing = False
2115 for a in [ "ssh-keygen", "useradd", "openssl", "sfdisk",
2116 "fdisk", "mkfs", "chpasswd", "sed", "grep", "sudo" ]:
2117 if Run("which " + a + " > /dev/null 2>&1"):
2118 Warn("Missing dependency: " + a)
2119 missing = True
2120 if missing == True:
2121 Warn("Please resolve missing dependencies listed for full functionality.")
2122 if UsesRpm():
2123 if not Run("rpm --quiet -q NetworkManager"):
2124 Error(GuestAgentLongName + " is not compatible with NetworkManager.")
2125 return 1
2126 if Run("rpm --quiet -q python-pyasn1"):
2127 Error(GuestAgentLongName + " requires python-pyasn1.")
2128 return 1
2129 if UsesDpkg() and Run("dpkg -l network-manager | grep -q ^un"):
2130 Error(GuestAgentLongName + " is not compatible with network-manager.")
2131 return 1
2132 for a in RulesFiles:
2133 if os.path.isfile(a):
2134 if os.path.isfile(GetLastPathElement(a)):
2135 os.remove(GetLastPathElement(a))
2136 shutil.move(a, ".")
2137 Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
2138 filename = "waagent"
2139 filepath = "/etc/init.d/" + filename
2140 distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
2141 if distro == 0:
2142 Error("Unable to detect Linux Distribution.")
2143 return 1
2144 init = [[Init_RedHat, "chkconfig --add " + filename],
2145 [Init_Debian, "update-rc.d " + filename + " defaults"],
2146 [Init_Suse, "insserv " + filename]][distro - 1]
2147 SetFileContents(filepath, init[0])
2148 os.chmod(filepath, 0755)
2149 Run(init[1])
2150 if os.path.isfile("/etc/waagent.conf"):
2151 try:
2152 os.remove("/etc/waagent.conf.old")
2153 except:
2154 pass
2155 try:
2156 os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
2157 Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
2158 except:
2159 pass
2160 SetFileContents("/etc/waagent.conf", WaagentConf)
2161 SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
2162 filepath = "/etc/ssh/sshd_config"
2163 ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
2164 a.startswith("ClientAliveInterval"),
2165 GetFileContents(filepath).split('\n'))) + "ClientAliveInterval 180\n")
2166 Log("Configured SSH client probing to keep connections alive.")
2167 ApplyVNUMAWorkaround()
2168 return 0
2169
2170def Uninstall():
2171 if IsWindows():
2172 print("ERROR: -uninstall invalid for windows, see waagent_service.exe")
2173 return 1
2174 SwitchCwd()
2175 for a in RulesFiles:
2176 if os.path.isfile(GetLastPathElement(a)):
2177 try:
2178 shutil.move(GetLastPathElement(a), a)
2179 Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
2180 except:
2181 pass
2182 filename = "waagent"
2183 a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
2184 if a == 0:
2185 Error("Unable to detect Linux Distribution.")
2186 return 1
2187 Run("service " + filename + " stop")
2188 cmd = ["chkconfig --del " + filename,
2189 "update-rc.d -f " + filename + " remove",
2190 "insserv -r " + filename][a - 1]
2191 Run(cmd)
2192 for f in os.listdir(LibDir) + ["/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
2193 try:
2194 os.remove(f)
2195 except:
2196 pass
2197 RevertVNUMAWorkaround()
2198 return 0
2199
2200def DeleteRootPassword():
2201 SetFileContents("/etc/shadow-temp", "")
2202 os.chmod("/etc/shadow-temp", 0000)
2203 Run("(echo root:*LOCK*:14600:::::: && grep -v ^root /etc/shadow ) > /etc/shadow-temp")
2204 Run("mv -f /etc/shadow-temp /etc/shadow")
2205 Log("Root password deleted.")
2206
2207def Deprovision(force, deluser):
2208 if IsWindows():
2209 Run(os.environ["windir"] + "\\system32\\sysprep\\sysprep.exe /generalize")
2210 return 0
2211
2212 SwitchCwd()
2213 ovfxml = GetFileContents("ovf-env.xml")
2214 ovfobj = None
2215 if ovfxml != None:
2216 ovfobj = OvfEnv().Parse(ovfxml)
2217
2218 print("WARNING! The waagent service will be stopped.")
2219 print("WARNING! All SSH host key pairs will be deleted.")
2220 print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
2221 print("WARNING! Cached DHCP leases will be deleted.")
2222
2223 delRootPass = Config.get("Provisioning.DeleteRootPassword")
2224 if delRootPass != None and delRootPass.lower().startswith("y"):
2225 print("WARNING! root password will be disabled. You will not be able to login as root.")
2226
2227 if ovfobj != None and deluser == True:
2228 print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
2229
2230 if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
2231 return 1
2232
2233 Run("service waagent stop")
2234
2235 if deluser == True:
2236 DeleteAccount(ovfobj.UserName)
2237
2238 # Remove SSH host keys
2239 regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
2240 if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
2241 Run("rm -f /etc/ssh/ssh_host_*key*")
2242
2243 # Remove root password
2244 if delRootPass != None and delRootPass.lower().startswith("y"):
2245 DeleteRootPassword()
2246
2247 # Remove distribution specific networking configuration
2248
2249 UpdateAndPublishHostNameCommon("localhost.localdomain")
2250
2251 # RedHat, Suse, Debian
2252 for a in VarLibDhcpDirectories:
2253 Run("rm -f " + a + "/*")
2254
2255 # Clear LibDir, remove nameserver and root bash history
2256 for f in os.listdir(LibDir) + ["/etc/resolv.conf", "/root/.bash_history", "/var/log/waagent.log"]:
2257 try:
2258 os.remove(f)
2259 except:
2260 pass
2261
2262 return 0
2263
2264def SwitchCwd():
2265 if not IsWindows():
2266 CreateDir(LibDir, "root", 0700)
2267 os.chdir(LibDir)
2268
2269def Usage():
2270 print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
2271 return 0
2272
2273if GuestAgentVersion == "":
2274 print("WARNING! This is a non-standard agent that does not include a valid version string.")
2275if IsLinux() and not DetectLinuxDistro():
2276 print("WARNING! Unable to detect Linux distribution. Some functionality may be broken.")
2277
2278if len(sys.argv) == 1:
2279 sys.exit(Usage())
2280
2281args = []
2282force = False
2283for a in sys.argv[1:]:
2284 if re.match("^([-/]*)(help|usage|\?)", a):
2285 sys.exit(Usage())
2286 elif re.match("^([-/]*)verbose", a):
2287 Verbose = True
2288 elif re.match("^([-/]*)force", a):
2289 force = True
2290 elif re.match("^([-/]*)(setup|install)", a):
2291 sys.exit(Install())
2292 elif re.match("^([-/]*)(uninstall)", a):
2293 sys.exit(Uninstall())
2294 else:
2295 args.append(a)
2296
2297Config = ConfigurationProvider()
2298
2299verbose = Config.get("Logs.Verbose")
2300if verbose != None and verbose.lower().startswith("y"):
2301 Verbose = True
2302
2303daemon = False
2304for a in args:
2305 if re.match("^([-/]*)deprovision\+user", a):
2306 sys.exit(Deprovision(force, True))
2307 elif re.match("^([-/]*)deprovision", a):
2308 sys.exit(Deprovision(force, False))
2309 elif re.match("^([-/]*)daemon", a):
2310 daemon = True
2311 elif re.match("^([-/]*)version", a):
2312 print(GuestAgentVersion + " running on " + LinuxDistro)
2313 sys.exit(0)
2314 elif re.match("^([-/]*)serialconsole", a):
2315 AddToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
2316 Log("Configured kernel to use ttyS0 as the boot console.")
2317 sys.exit(0)
2318 else:
2319 print("Invalid command line parameter:" + a)
2320 sys.exit(1)
2321
2322if daemon == False:
2323 sys.exit(Usage())
2324
2325try:
2326 SwitchCwd()
2327 Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
2328 if IsLinux():
2329 Log("Linux Distribution Detected : " + LinuxDistro)
2330 WaAgent = Agent()
2331 WaAgent.Run()
2332except Exception, e:
2333 Error(traceback.format_exc())
2334 Error("Exception: " + str(e))
2335 sys.exit(1)
23360
=== removed directory '.pc/001_ubuntu_agent_startup.patch'
=== removed file '.pc/001_ubuntu_agent_startup.patch/waagent'
--- .pc/001_ubuntu_agent_startup.patch/waagent 2012-06-22 09:10:22 +0000
+++ .pc/001_ubuntu_agent_startup.patch/waagent 1970-01-01 00:00:00 +0000
@@ -1,2395 +0,0 @@
1#!/usr/bin/python
2#
3# Windows Azure Linux Agent
4#
5# Copyright 2012 Microsoft Corporation
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# Requires Python 2.4+ and Openssl 1.0+
20#
21# Implements parts of RFC 2131, 1541, 1497 and
22# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
23# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
24#
25
26import array
27import base64
28import httplib
29import os
30import os.path
31import platform
32import pwd
33import re
34import shutil
35import socket
36import SocketServer
37import struct
38import sys
39import tempfile
40import textwrap
41import threading
42import time
43import traceback
44import xml.dom.minidom
45
46GuestAgentName = "WALinuxAgent"
47GuestAgentLongName = "Windows Azure Linux Agent"
48GuestAgentVersion = "rd_wala.120504-1323"
49ProtocolVersion = "2011-12-31"
50
51Config = None
52LinuxDistro = "UNKNOWN"
53Verbose = False
54WaAgent = None
55DiskActivated = False
56Openssl = "openssl"
57
58PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"]
59RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
60 "/etc/udev/rules.d/70-persistent-net.rules" ]
61VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
62EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
63LibDir = "/var/lib/waagent"
64
65# This lets us index into a string or an array of integers transparently.
66def Ord(a):
67 if type(a) == type("a"):
68 a = ord(a)
69 return a
70
71def IsWindows():
72 return (platform.uname()[0] == "Windows")
73
74def IsLinux():
75 return (platform.uname()[0] == "Linux")
76
77def DetectLinuxDistro():
78 global LinuxDistro
79 if os.path.isfile("/etc/redhat-release"):
80 LinuxDistro = "RedHat"
81 return True
82 if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"):
83 LinuxDistro = "Ubuntu"
84 return True
85 if os.path.isfile("/etc/debian_version"):
86 LinuxDistro = "Debian"
87 return True
88 if os.path.isfile("/etc/SuSE-release"):
89 LinuxDistro = "Suse"
90 return True
91 return False
92
93def IsRedHat():
94 return "RedHat" in LinuxDistro
95
96def IsUbuntu():
97 return "Ubuntu" in LinuxDistro
98
99def IsDebian():
100 return IsUbuntu() or "Debian" in LinuxDistro
101
102def IsSuse():
103 return "Suse" in LinuxDistro
104
105def UsesRpm():
106 return IsRedHat() or IsSuse()
107
108def UsesDpkg():
109 return IsDebian()
110
111def GetLastPathElement(path):
112 return path.rsplit('/', 1)[1]
113
114def GetFileContents(filepath):
115 file = None
116 try:
117 file = open(filepath)
118 except:
119 return None
120 if file == None:
121 return None
122 try:
123 return file.read()
124 finally:
125 file.close()
126
127def SetFileContents(filepath, contents):
128 file = open(filepath, "w")
129 try:
130 file.write(contents)
131 finally:
132 file.close()
133
134def AppendFileContents(filepath, contents):
135 file = open(filepath, "a")
136 try:
137 file.write(contents)
138 finally:
139 file.close()
140
141def ReplaceFileContentsAtomic(filepath, contents):
142 handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
143 try:
144 os.write(handle, contents)
145 finally:
146 os.close(handle)
147 try:
148 os.rename(temp, filepath)
149 return
150 except:
151 pass
152 os.remove(filepath)
153 os.rename(temp, filepath)
154
155def GetLineStartingWith(prefix, filepath):
156 for line in GetFileContents(filepath).split('\n'):
157 if line.startswith(prefix):
158 return line
159 return None
160
161def Run(a):
162 LogIfVerbose(a)
163 return os.system(a)
164
165def GetNodeTextData(a):
166 for b in a.childNodes:
167 if b.nodeType == b.TEXT_NODE:
168 return b.data
169
170def GetHome():
171 home = None
172 try:
173 home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
174 except:
175 pass
176 if (home == None) or (home.startswith("/") == False):
177 home = "/home"
178 return home
179
180def ChangeOwner(filepath, user):
181 p = None
182 try:
183 p = pwd.getpwnam(user)
184 except:
185 pass
186 if p != None:
187 os.chown(filepath, p[2], p[3])
188
189def CreateDir(dirpath, user, mode):
190 try:
191 os.makedirs(dirpath, mode)
192 except:
193 pass
194 ChangeOwner(dirpath, user)
195
196def CreateAccount(user, password, expiration, thumbprint):
197 if IsWindows():
198 Log("Skipping CreateAccount on Windows")
199 return None
200 userentry = None
201 try:
202 userentry = pwd.getpwnam(user)
203 except:
204 pass
205 uidmin = None
206 try:
207 uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
208 except:
209 pass
210 if uidmin == None:
211 uidmin = 100
212 if userentry != None and userentry[2] < uidmin:
213 Error("CreateAccount: " + user + " is a system user. Will not set password.")
214 return "Failed to set password for system user: " + user + " (0x06)."
215 if userentry == None:
216 command = "useradd -m " + user
217 if expiration != None:
218 command += " -e " + expiration.split('.')[0]
219 if Run(command):
220 Error("Failed to create user account: " + user)
221 return "Failed to create user account: " + user + " (0x07)."
222 else:
223 Log("CreateAccount: " + user + " already exists. Will update password.")
224 if password != None:
225 os.popen("chpasswd", "w").write(user + ":" + password + "\n")
226 try:
227 if password == None:
228 SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
229 else:
230 SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
231 os.chmod("/etc/sudoers.d/waagent", 0440)
232 except:
233 Error("CreateAccount: Failed to configure sudo access for user.")
234 return "Failed to configure sudo privileges (0x08)."
235 home = GetHome()
236 if thumbprint != None:
237 dir = home + "/" + user + "/.ssh"
238 CreateDir(dir, user, 0700)
239 pub = dir + "/id_rsa.pub"
240 prv = dir + "/id_rsa"
241 Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
242 SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
243 for f in [pub, prv]:
244 os.chmod(f, 0600)
245 ChangeOwner(f, user)
246 SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
247 ChangeOwner(dir + "/authorized_keys", user)
248 Log("Created user account: " + user)
249 return None
250
251def DeleteAccount(user):
252 if IsWindows():
253 Log("Skipping DeleteAccount on Windows")
254 return
255 userentry = None
256 try:
257 userentry = pwd.getpwnam(user)
258 except:
259 pass
260 if userentry == None:
261 Error("DeleteAccount: " + user + " not found.")
262 return
263 uidmin = None
264 try:
265 uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
266 except:
267 pass
268 if uidmin == None:
269 uidmin = 100
270 if userentry[2] < uidmin:
271 Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
272 return
273 Run("userdel -f -r " + user)
274 try:
275 os.remove("/etc/sudoers.d/waagent")
276 except:
277 pass
278 return
279
280def ReloadSshd():
281 name = None
282 if IsRedHat() or IsSuse():
283 name = "sshd"
284 if IsDebian():
285 name = "ssh"
286 if name == None:
287 return
288 if not Run("service " + name + " status | grep running"):
289 Run("service " + name + " reload")
290
291def IsInRangeInclusive(a, low, high):
292 return (a >= low and a <= high)
293
294def IsPrintable(ch):
295 return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
296
297def HexDump(buffer, size):
298 if size < 0:
299 size = len(buffer)
300 result = ""
301 for i in range(0, size):
302 if (i % 16) == 0:
303 result += "%06X: " % i
304 byte = struct.unpack("B", buffer[i])[0]
305 result += "%02X " % byte
306 if (i & 15) == 7:
307 result += " "
308 if ((i + 1) % 16) == 0 or (i + 1) == size:
309 j = i
310 while ((j + 1) % 16) != 0:
311 result += " "
312 if (j & 7) == 7:
313 result += " "
314 j += 1
315 result += " "
316 for j in range(i - (i % 16), i + 1):
317 byte = struct.unpack("B", buffer[j])[0]
318 k = '.'
319 if IsPrintable(byte):
320 k = chr(byte)
321 result += k
322 if (i + 1) != size:
323 result += "\n"
324 return result
325
326def ThrottleLog(counter):
327 # Log everything up to 10, every 10 up to 100, then every 100.
328 return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
329
330def Logger():
331 class T(object):
332 def __init__(self):
333 self.File = None
334
335 self = T()
336
337 def LogToFile(message):
338 FilePath = ["/var/log/waagent.log", "waagent.log"][IsWindows()]
339 if not os.path.isfile(FilePath) and self.File != None:
340 self.File.close()
341 self.File = None
342 if self.File == None:
343 self.File = open(FilePath, "a")
344 self.File.write(message + "\n")
345 self.File.flush()
346
347 def Log(message):
348 LogWithPrefix("", message)
349
350 def LogWithPrefix(prefix, message):
351 t = time.localtime()
352 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)
353 t += prefix
354 for line in message.split('\n'):
355 line = t + line
356 print(line)
357 LogToFile(line)
358
359 return Log, LogWithPrefix
360
361Log, LogWithPrefix = Logger()
362
363def NoLog(message):
364 pass
365
366def LogIfVerbose(message):
367 if Verbose == True:
368 Log(message)
369
370def LogWithPrefixIfVerbose(prefix, message):
371 if Verbose == True:
372 LogWithPrefix(prefix, message)
373
374def Warn(message):
375 LogWithPrefix("WARNING:", message)
376
377def Error(message):
378 LogWithPrefix("ERROR:", message)
379
380def ErrorWithPrefix(prefix, message):
381 LogWithPrefix("ERROR:" + prefix, message)
382
383def Linux_ioctl_GetIpv4Address(ifname):
384 import fcntl
385 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
386 return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
387
388def Linux_ioctl_GetInterfaceMac(ifname):
389 import fcntl
390 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
391 info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
392 return ''.join(['%02X' % Ord(char) for char in info[18:24]])
393
394def GetIpv4Address():
395 if IsLinux():
396 for ifname in PossibleEthernetInterfaces:
397 try:
398 return Linux_ioctl_GetIpv4Address(ifname)
399 except IOError, e:
400 pass
401 else:
402 try:
403 return socket.gethostbyname(socket.gethostname())
404 except Exception, e:
405 ErrorWithPrefix("GetIpv4Address:", str(e))
406 ErrorWithPrefix("GetIpv4Address:", traceback.format_exc())
407
408def HexStringToByteArray(a):
409 b = ""
410 for c in range(0, len(a) / 2):
411 b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
412 return b
413
414def GetMacAddress():
415 if IsWindows():
416 # Windows: Physical Address. . . . . . . . . : 00-15-17-79-00-7F\n
417 a = "ipconfig /all | findstr /c:\"Physical Address\" | findstr /v \"00-00-00-00-00-00-00\""
418 a = os.popen(a).read()
419 a = re.sub("\s+$", "", a)
420 a = re.sub(".+ ", "", a)
421 a = re.sub(":", "", a)
422 a = re.sub("-", "", a)
423 else:
424 for ifname in PossibleEthernetInterfaces:
425 try:
426 a = Linux_ioctl_GetInterfaceMac(ifname)
427 break
428 except IOError, e:
429 pass
430 return HexStringToByteArray(a)
431
432def DeviceForIdePort(n):
433 if n > 3:
434 return None
435 g0 = "00000000"
436 if n > 1:
437 g0 = "00000001"
438 n = n - 2
439 device = None
440 path="/sys/bus/vmbus/devices/"
441 for vmbus in os.listdir(path):
442 guid=GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
443 if guid[0] == g0 and guid[1] == "000" + str(n):
444 for root, dirs, files in os.walk(path + vmbus):
445 if root.endswith("/block"):
446 device = dirs[0]
447 break
448 break
449 return device
450
451class Util(object):
452 def _HttpGet(self, url, headers):
453 LogIfVerbose("HttpGet(" + url + ")")
454 maxRetry = 2
455 if url.startswith("http://"):
456 url = url[7:]
457 url = url[url.index("/"):]
458 for retry in range(0, maxRetry + 1):
459 strRetry = str(retry)
460 log = [NoLog, Log][retry > 0]
461 log("retry HttpGet(" + url + "),retry=" + strRetry)
462 response = None
463 strStatus = "None"
464 try:
465 httpConnection = httplib.HTTPConnection(self.Endpoint)
466 if headers == None:
467 request = httpConnection.request("GET", url)
468 else:
469 request = httpConnection.request("GET", url, None, headers)
470 response = httpConnection.getresponse()
471 strStatus = str(response.status)
472 except:
473 pass
474 log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
475 if response == None or response.status != httplib.OK:
476 Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
477 if retry == maxRetry:
478 Log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
479 return None
480 else:
481 Log("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
482 time.sleep(10)
483 else:
484 log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
485 return response.read()
486
487 def HttpGetWithoutHeaders(self, url):
488 return self._HttpGet(url, None)
489
490 def HttpGetWithHeaders(self, url):
491 return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion})
492
493 def HttpSecureGetWithHeaders(self, url, transportCert):
494 return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName,
495 "x-ms-version": ProtocolVersion,
496 "x-ms-cipher-name": "DES_EDE3_CBC",
497 "x-ms-guest-agent-public-x509-cert": transportCert})
498
499 def HttpPost(self, url, data):
500 LogIfVerbose("HttpPost(" + url + ")")
501 maxRetry = 2
502 for retry in range(0, maxRetry + 1):
503 strRetry = str(retry)
504 log = [NoLog, Log][retry > 0]
505 log("retry HttpPost(" + url + "),retry=" + strRetry)
506 response = None
507 strStatus = "None"
508 try:
509 httpConnection = httplib.HTTPConnection(self.Endpoint)
510 request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName,
511 "Content-Type": "text/xml; charset=utf-8",
512 "x-ms-version": ProtocolVersion})
513 response = httpConnection.getresponse()
514 strStatus = str(response.status)
515 except:
516 pass
517 log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
518 if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED):
519 Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
520 if retry == maxRetry:
521 Log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
522 return None
523 else:
524 Log("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
525 time.sleep(10)
526 else:
527 log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
528 return response
529
530def LoadBalancerProbeServer(port):
531
532 class T(object):
533 def __init__(self, port):
534 enabled = Config.get("LBProbeResponder")
535 if enabled != None and enabled.lower().startswith("n"):
536 return
537 self.ProbeCounter = 0
538 self.server = SocketServer.TCPServer((GetIpv4Address(), port), TCPHandler)
539 self.server_thread = threading.Thread(target = self.server.serve_forever)
540 self.server_thread.setDaemon(True)
541 self.server_thread.start()
542
543 def shutdown(self):
544 global EnableLoadBalancerProbes
545 if not EnableLoadBalancerProbes:
546 return
547 self.server.shutdown()
548
549 class TCPHandler(SocketServer.BaseRequestHandler):
550 def GetHttpDateTimeNow(self):
551 # Date: Fri, 25 Mar 2011 04:53:10 GMT
552 return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
553
554 def handle(self):
555 context.ProbeCounter = (context.ProbeCounter + 1) % 1000000
556 log = [NoLog, LogIfVerbose][ThrottleLog(context.ProbeCounter)]
557 strCounter = str(context.ProbeCounter)
558 if context.ProbeCounter == 1:
559 Log("Receiving LB probes.")
560 log("Received LB probe # " + strCounter)
561 self.request.recv(1024)
562 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")
563
564 context = T(port)
565 return context
566
567class ConfigurationProvider(object):
568 def __init__(self):
569 self.values = dict()
570 if os.path.isfile("/etc/waagent.conf") == False:
571 raise Exception("Missing configuration in /etc/waagent.conf")
572 try:
573 for line in GetFileContents("/etc/waagent.conf").split('\n'):
574 if not line.startswith("#") and "=" in line:
575 parts = line.split()[0].split('=')
576 value = parts[1].strip("\" ")
577 if value != "None":
578 self.values[parts[0]] = value
579 else:
580 self.values[parts[0]] = None
581 except:
582 Error("Unable to parse /etc/waagent.conf")
583 raise
584 return
585
586 def get(self, key):
587 return self.values.get(key)
588
589class EnvMonitor(object):
590 def __init__(self):
591 self.shutdown = False
592 self.HostName = socket.gethostname()
593 self.server_thread = threading.Thread(target = self.monitor)
594 self.server_thread.setDaemon(True)
595 self.server_thread.start()
596 self.published = False
597
598 def monitor(self):
599 publish = Config.get("Provisioning.MonitorHostName")
600 dhcpcmd = "pidof dhclient"
601 if IsSuse():
602 dhcpcmd = "pidof dhcpcd"
603 if IsDebian():
604 dhcpcmd = "pidof dhclient3"
605 dhcppid = os.popen(dhcpcmd).read()
606 while not self.shutdown:
607 for a in RulesFiles:
608 if os.path.isfile(a):
609 if os.path.isfile(GetLastPathElement(a)):
610 os.remove(GetLastPathElement(a))
611 shutil.move(a, ".")
612 Log("EnvMonitor: Moved " + a + " -> " + LibDir)
613 if publish != None and publish.lower().startswith("y"):
614 try:
615 if socket.gethostname() != self.HostName:
616 Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
617 self.HostName = socket.gethostname()
618 WaAgent.UpdateAndPublishHostName(self.HostName)
619 dhcppid = os.popen(dhcpcmd).read()
620 self.published = True
621 except:
622 pass
623 else:
624 self.published = True
625 pid = ""
626 if not os.path.isdir("/proc/" + dhcppid.strip()):
627 pid = os.popen(dhcpcmd).read()
628 if pid != "" and pid != dhcppid:
629 Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
630 WaAgent.RestoreRoutes()
631 dhcppid = pid
632 time.sleep(5)
633
634 def SetHostName(self, name):
635 if socket.gethostname() == name:
636 self.published = True
637 else:
638 Run("hostname " + name)
639
640 def IsNamePublished(self):
641 return self.published
642
643 def shutdown(self):
644 self.shutdown = True
645 self.server_thread.join()
646
647class Certificates(object):
648#
649# <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
650# <Version>2010-12-15</Version>
651# <Incarnation>2</Incarnation>
652# <Format>Pkcs7BlobWithPfxContents</Format>
653# <Data>MIILTAY...
654# </Data>
655# </CertificateFile>
656#
657 def __init__(self):
658 self.reinitialize()
659
660 def reinitialize(self):
661 self.Incarnation = None
662 self.Role = None
663
664 def Parse(self, xmlText):
665 self.reinitialize()
666 SetFileContents("Certificates.xml", xmlText)
667 dom = xml.dom.minidom.parseString(xmlText)
668 for a in [ "CertificateFile", "Version", "Incarnation",
669 "Format", "Data", ]:
670 if not dom.getElementsByTagName(a):
671 Error("Certificates.Parse: Missing " + a)
672 return None
673 node = dom.childNodes[0]
674 if node.localName != "CertificateFile":
675 Error("Certificates.Parse: root not CertificateFile")
676 return None
677 SetFileContents("Certificates.p7m",
678 "MIME-Version: 1.0\n"
679 + "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
680 + "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
681 + "Content-Transfer-Encoding: base64\n\n"
682 + GetNodeTextData(dom.getElementsByTagName("Data")[0]))
683 if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
684 Error("Certificates.Parse: Failed to extract certificates from CMS message.")
685 return self
686 # There may be multiple certificates in this package. Split them.
687 file = open("Certificates.pem")
688 pindex = 1
689 cindex = 1
690 output = open("temp.pem", "w")
691 for line in file.readlines():
692 output.write(line)
693 if line.startswith("-----END PRIVATE KEY-----") or line.startswith("-----END CERTIFICATE-----"):
694 output.close()
695 if line.startswith("-----END PRIVATE KEY-----"):
696 os.rename("temp.pem", str(pindex) + ".prv")
697 pindex += 1
698 else:
699 os.rename("temp.pem", str(cindex) + ".crt")
700 cindex += 1
701 output = open("temp.pem", "w")
702 output.close()
703 os.remove("temp.pem")
704 keys = dict()
705 index = 1
706 filename = str(index) + ".crt"
707 while os.path.isfile(filename):
708 thumbprint = os.popen(Openssl + " x509 -in " + filename + " -fingerprint -noout").read().rstrip().split('=')[1].replace(':', '').upper()
709 pubkey=os.popen(Openssl + " x509 -in " + filename + " -pubkey -noout").read()
710 keys[pubkey] = thumbprint
711 os.rename(filename, thumbprint + ".crt")
712 os.chmod(thumbprint + ".crt", 0600)
713 if IsRedHat():
714 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + thumbprint + ".crt")
715 index += 1
716 filename = str(index) + ".crt"
717 index = 1
718 filename = str(index) + ".prv"
719 while os.path.isfile(filename):
720 pubkey = os.popen(Openssl + " rsa -in " + filename + " -pubout").read()
721 os.rename(filename, keys[pubkey] + ".prv")
722 os.chmod(keys[pubkey] + ".prv", 0600)
723 if IsRedHat():
724 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + keys[pubkey] + ".prv")
725 index += 1
726 filename = str(index) + ".prv"
727 return self
728
729class SharedConfig(object):
730#
731# <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
732# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
733# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
734# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
735# </Deployment>
736# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
737# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
738# <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
739# <Probes>
740# <Probe name="MachineRole" />
741# <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
742# <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
743# </Probes>
744# </LoadBalancerSettings>
745# <OutputEndpoints>
746# <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
747# <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
748# </Endpoint>
749# </OutputEndpoints>
750# <Instances>
751# <Instance id="MachineRole_IN_0" address="10.115.153.75">
752# <FaultDomains randomId="0" updateId="0" updateCount="0" />
753# <InputEndpoints>
754# <Endpoint name="a" address="10.115.153.75:80" protocol="http" isPublic="true" loadBalancedPublicAddress="70.37.106.197:80" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
755# <LocalPorts>
756# <LocalPortRange from="80" to="80" />
757# </LocalPorts>
758# </Endpoint>
759# <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
760# <LocalPorts>
761# <LocalPortRange from="3389" to="3389" />
762# </LocalPorts>
763# </Endpoint>
764# <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" address="10.115.153.75:20000" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.106.197:3389" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
765# <LocalPorts>
766# <LocalPortRange from="20000" to="20000" />
767# </LocalPorts>
768# </Endpoint>
769# </InputEndpoints>
770# </Instance>
771# </Instances>
772# </SharedConfig>
773#
774 def __init__(self):
775 self.reinitialize()
776
777 def reinitialize(self):
778 self.Deployment = None
779 self.Incarnation = None
780 self.Role = None
781 self.LoadBalancerSettings = None
782 self.OutputEndpoints = None
783 self.Instances = None
784
785 def Parse(self, xmlText):
786 self.reinitialize()
787 SetFileContents("SharedConfig.xml", xmlText)
788 dom = xml.dom.minidom.parseString(xmlText)
789 for a in [ "SharedConfig", "Deployment", "Service",
790 "ServiceInstance", "Incarnation", "Role", ]:
791 if not dom.getElementsByTagName(a):
792 Error("SharedConfig.Parse: Missing " + a)
793 return None
794 node = dom.childNodes[0]
795 if node.localName != "SharedConfig":
796 Error("SharedConfig.Parse: root not SharedConfig")
797 return None
798 program = Config.get("Role.TopologyConsumer")
799 if program != None:
800 os.spawnl(os.P_NOWAIT, program, program, LibDir + "/SharedConfig.xml")
801 return self
802
803class HostingEnvironmentConfig(object):
804#
805# <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
806# <StoredCertificates>
807# <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
808# </StoredCertificates>
809# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
810# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
811# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
812# </Deployment>
813# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
814# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
815# <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip">
816# <CAS mode="full" />
817# <PrivilegeLevel mode="max" />
818# <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties>
819# </HostingEnvironmentSettings>
820# <ApplicationSettings>
821# <Setting name="__ModelData" value="&lt;m role=&quot;MachineRole&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;MachineRole&quot;>&lt;e name=&quot;a&quot; />&lt;e name=&quot;b&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;/r>&lt;/m>" />
822# <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." />
823# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcN..." />
824# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2022-07-23T23:59:59.0000000-07:00" />
825# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test" />
826# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" />
827# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
828# <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" />
829# </ApplicationSettings>
830# <ResourceReferences>
831# <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" />
832# </ResourceReferences>
833# </HostingEnvironmentConfig>
834#
835 def __init__(self):
836 self.reinitialize()
837
838 def reinitialize(self):
839 self.StoredCertificates = None
840 self.Deployment = None
841 self.Incarnation = None
842 self.Role = None
843 self.HostingEnvironmentSettings = None
844 self.ApplicationSettings = None
845 self.Certificates = None
846 self.ResourceReferences = None
847
848 def Parse(self, xmlText):
849 self.reinitialize()
850 SetFileContents("HostingEnvironmentConfig.xml", xmlText)
851 dom = xml.dom.minidom.parseString(xmlText)
852 for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
853 "ServiceInstance", "Incarnation", "Role", ]:
854 if not dom.getElementsByTagName(a):
855 Error("HostingEnvironmentConfig.Parse: Missing " + a)
856 return None
857 node = dom.childNodes[0]
858 if node.localName != "HostingEnvironmentConfig":
859 Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
860 return None
861 self.ApplicationSettings = dom.getElementsByTagName("Setting")
862 self.Certificates = dom.getElementsByTagName("StoredCertificate")
863 return self
864
865 def DecryptPassword(self, e):
866 SetFileContents("password.p7m",
867 "MIME-Version: 1.0\n"
868 + "Content-Disposition: attachment; filename=\"password.p7m\"\n"
869 + "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
870 + "Content-Transfer-Encoding: base64\n\n"
871 + textwrap.fill(e, 64))
872 return os.popen(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem").read()
873
874 def ActivateResourceDisk(self):
875 global DiskActivated
876 if IsWindows():
877 DiskActivated = True
878 Log("Skipping ActivateResourceDisk on Windows")
879 return
880 format = Config.get("ResourceDisk.Format")
881 if format == None or format.lower().startswith("n"):
882 DiskActivated = True
883 return
884 device = DeviceForIdePort(1)
885 if device == None:
886 Error("ActivateResourceDisk: Unable to detect disk topology.")
887 return
888 device = "/dev/" + device
889 for entry in os.popen("mount").read().split():
890 if entry.startswith(device + "1"):
891 Log("ActivateResourceDisk: " + device + "1 is already mounted.")
892 DiskActivated = True
893 return
894 mountpoint = Config.get("ResourceDisk.MountPoint")
895 if mountpoint == None:
896 mountpoint = "/mnt/resource"
897 CreateDir(mountpoint, "root", 0755)
898 fs = Config.get("ResourceDisk.Filesystem")
899 if fs == None:
900 fs = "ext3"
901 if os.popen("sfdisk -q -c " + device + " 1").read().rstrip() == "7" and fs != "ntfs":
902 Run("sfdisk -c " + device + " 1 83")
903 Run("mkfs." + fs + " " + device + "1")
904 if Run("mount " + device + "1 " + mountpoint):
905 Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).")
906 return
907 Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs)
908 DiskActivated = True
909 swap = Config.get("ResourceDisk.EnableSwap")
910 if swap == None or swap.lower().startswith("n"):
911 return
912 sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
913 if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
914 os.remove(mountpoint + "/swapfile")
915 if not os.path.isfile(mountpoint + "/swapfile"):
916 Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
917 Run("mkswap " + mountpoint + "/swapfile")
918 if not Run("swapon " + mountpoint + "/swapfile"):
919 Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
920 else:
921 Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
922
923 def Process(self):
924 if DiskActivated == False:
925 diskThread = threading.Thread(target = self.ActivateResourceDisk)
926 diskThread.start()
927 User = None
928 Pass = None
929 Expiration = None
930 Thumbprint = None
931 for b in self.ApplicationSettings:
932 sname = b.getAttribute("name")
933 svalue = b.getAttribute("value")
934 if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword":
935 Pass = self.DecryptPassword(svalue)
936 elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername":
937 User = svalue
938 elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration":
939 Expiration = svalue
940 elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption":
941 Thumbprint = svalue.split(':')[1].upper()
942 if User != None and Pass != None:
943 if User != "root" and User != "" and Pass != "":
944 CreateAccount(User, Pass, Expiration, Thumbprint)
945 else:
946 Error("Not creating user account: user=" + User + " pass=" + Pass)
947 for c in self.Certificates:
948 cname = c.getAttribute("name")
949 csha1 = c.getAttribute("certificateId").split(':')[1].upper()
950 cpath = c.getAttribute("storeName")
951 clevel = c.getAttribute("configurationLevel")
952 if os.path.isfile(csha1 + ".prv"):
953 Log("Private key with thumbprint: " + csha1 + " was retrieved.")
954 if os.path.isfile(csha1 + ".crt"):
955 Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
956 program = Config.get("Role.ConfigurationConsumer")
957 if program != None:
958 os.spawnl(os.P_NOWAIT, program, program, LibDir + "/HostingEnvironmentConfig.xml")
959
960class GoalState(Util):
961#
962# <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
963# <Version>2010-12-15</Version>
964# <Incarnation>1</Incarnation>
965# <Machine>
966# <ExpectedState>Started</ExpectedState>
967# <LBProbePorts>
968# <Port>16001</Port>
969# </LBProbePorts>
970# </Machine>
971# <Container>
972# <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
973# <RoleInstanceList>
974# <RoleInstance>
975# <InstanceId>MachineRole_IN_0</InstanceId>
976# <State>Started</State>
977# <Configuration>
978# <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=hostingEnvironmentConfig&amp;incarnation=1</HostingEnvironmentConfig>
979# <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=sharedConfig&amp;incarnation=1</SharedConfig>
980# <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&amp;incarnation=1</Certificates>
981# </Configuration>
982# </RoleInstance>
983# </RoleInstanceList>
984# </Container>
985# </GoalState>
986#
987# There is only one Role for VM images.
988#
989# Of primary interest is:
990# Machine/ExpectedState -- this is how shutdown is requested
991# LBProbePorts -- an http server needs to run here
992# We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
993# And of course, Incarnation
994#
995 def __init__(self, Agent):
996 self.Agent = Agent
997 self.Endpoint = Agent.Endpoint
998 self.TransportCert = Agent.TransportCert
999 self.reinitialize()
1000
1001 def reinitialize(self):
1002 self.Incarnation = None # integer
1003 self.ExpectedState = None # "Started" or "Stopped"
1004 self.HostingEnvironmentConfigUrl = None
1005 self.HostingEnvironmentConfigXml = None
1006 self.HostingEnvironmentConfig = None
1007 self.SharedConfigUrl = None
1008 self.SharedConfigXml = None
1009 self.SharedConfig = None
1010 self.CertificatesUrl = None
1011 self.CertificatesXml = None
1012 self.Certificates = None
1013 self.RoleInstanceId = None
1014 self.ContainerId = None
1015 self.LoadBalancerProbePort = None # integer, ?list of integers
1016
1017 def Parse(self, xmlText):
1018 self.reinitialize()
1019 node = xml.dom.minidom.parseString(xmlText).childNodes[0]
1020 if node.localName != "GoalState":
1021 Error("GoalState.Parse: root not GoalState")
1022 return None
1023 for a in node.childNodes:
1024 if a.nodeType == node.ELEMENT_NODE:
1025 if a.localName == "Incarnation":
1026 self.Incarnation = GetNodeTextData(a)
1027 elif a.localName == "Machine":
1028 for b in a.childNodes:
1029 if b.nodeType == node.ELEMENT_NODE:
1030 if b.localName == "ExpectedState":
1031 self.ExpectedState = GetNodeTextData(b)
1032 Log("ExpectedState: " + self.ExpectedState)
1033 elif b.localName == "LBProbePorts":
1034 for c in b.childNodes:
1035 if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
1036 self.LoadBalancerProbePort = int(GetNodeTextData(c))
1037 elif a.localName == "Container":
1038 for b in a.childNodes:
1039 if b.nodeType == node.ELEMENT_NODE:
1040 if b.localName == "ContainerId":
1041 self.ContainerId = GetNodeTextData(b)
1042 Log("ContainerId: " + self.ContainerId)
1043 elif b.localName == "RoleInstanceList":
1044 for c in b.childNodes:
1045 if c.localName == "RoleInstance":
1046 for d in c.childNodes:
1047 if d.nodeType == node.ELEMENT_NODE:
1048 if d.localName == "InstanceId":
1049 self.RoleInstanceId = GetNodeTextData(d)
1050 Log("RoleInstanceId: " + self.RoleInstanceId)
1051 elif d.localName == "State":
1052 pass
1053 elif d.localName == "Configuration":
1054 for e in d.childNodes:
1055 if e.nodeType == node.ELEMENT_NODE:
1056 if e.localName == "HostingEnvironmentConfig":
1057 self.HostingEnvironmentConfigUrl = GetNodeTextData(e)
1058 LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl)
1059 self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
1060 self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
1061 elif e.localName == "SharedConfig":
1062 self.SharedConfigUrl = GetNodeTextData(e)
1063 LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
1064 self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
1065 self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
1066 elif e.localName == "Certificates":
1067 self.CertificatesUrl = GetNodeTextData(e)
1068 LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl)
1069 self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
1070 self.Certificates = Certificates().Parse(self.CertificatesXml)
1071 if self.Incarnation == None:
1072 Error("GoalState.Parse: Incarnation missing")
1073 return None
1074 if self.ExpectedState == None:
1075 Error("GoalState.Parse: ExpectedState missing")
1076 return None
1077 if self.RoleInstanceId == None:
1078 Error("GoalState.Parse: RoleInstanceId missing")
1079 return None
1080 if self.ContainerId == None:
1081 Error("GoalState.Parse: ContainerId missing")
1082 return None
1083 SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
1084 return self
1085
1086 def Process(self):
1087 self.HostingEnvironmentConfig.Process()
1088
1089class OvfEnv(object):
1090#
1091# <?xml version="1.0" encoding="utf-8"?>
1092# <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1093# <wa:ProvisioningSection>
1094# <wa:Version>1.0</wa:Version>
1095# <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
1096# <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
1097# <HostName>HostName</HostName>
1098# <UserName>UserName</UserName>
1099# <UserPassword>UserPassword</UserPassword>
1100# <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
1101# <SSH>
1102# <PublicKeys>
1103# <PublicKey>
1104# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
1105# <Path>$HOME/UserName/.ssh/authorized_keys</Path>
1106# </PublicKey>
1107# </PublicKeys>
1108# <KeyPairs>
1109# <KeyPair>
1110# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
1111# <Path>$HOME/UserName/.ssh/id_rsa</Path>
1112# </KeyPair>
1113# </KeyPairs>
1114# </SSH>
1115# </LinuxProvisioningConfigurationSet>
1116# </wa:ProvisioningSection>
1117# </Environment>
1118#
1119 def __init__(self):
1120 self.reinitialize()
1121
1122 def reinitialize(self):
1123 self.WaNs = "http://schemas.microsoft.com/windowsazure"
1124 self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
1125 self.MajorVersion = 1
1126 self.MinorVersion = 0
1127 self.ComputerName = None
1128 self.AdminPassword = None
1129 self.UserName = None
1130 self.UserPassword = None
1131 self.DisableSshPasswordAuthentication = True
1132 self.SshPublicKeys = []
1133 self.SshKeyPairs = []
1134
1135 def Parse(self, xmlText):
1136 self.reinitialize()
1137 dom = xml.dom.minidom.parseString(xmlText)
1138 if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
1139 Error("Unable to parse OVF XML.")
1140 section = None
1141 newer = False
1142 for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
1143 for n in p.childNodes:
1144 if n.localName == "Version":
1145 verparts = GetNodeTextData(n).split('.')
1146 major = int(verparts[0])
1147 minor = int(verparts[1])
1148 if major > self.MajorVersion:
1149 newer = True
1150 if major != self.MajorVersion:
1151 break
1152 if minor > self.MinorVersion:
1153 newer = True
1154 section = p
1155 if newer == True:
1156 Warn("Newer provisioning configuration detected. Please consider updating waagent.")
1157 if section == None:
1158 Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
1159 return None
1160 self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
1161 self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
1162 try:
1163 self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
1164 except:
1165 pass
1166 disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
1167 if len(disableSshPass) != 0:
1168 self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true")
1169 for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"):
1170 fp = None
1171 path = None
1172 for c in pkey.childNodes:
1173 if c.localName == "Fingerprint":
1174 fp = GetNodeTextData(c).upper()
1175 if c.localName == "Path":
1176 path = GetNodeTextData(c)
1177 self.SshPublicKeys += [[fp, path]]
1178 for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
1179 fp = None
1180 path = None
1181 for c in keyp.childNodes:
1182 if c.localName == "Fingerprint":
1183 fp = GetNodeTextData(c).upper()
1184 if c.localName == "Path":
1185 path = GetNodeTextData(c)
1186 self.SshKeyPairs += [[fp, path]]
1187 return self
1188
1189 def PrepareDir(self, filepath):
1190 home = GetHome()
1191 # Expand HOME variable if present in path
1192 path = os.path.normpath(filepath.replace("$HOME", home))
1193 if (path.startswith("/") == False) or (path.endswith("/") == True):
1194 return None
1195 dir = path.rsplit('/', 1)[0]
1196 if dir != "":
1197 CreateDir(dir, "root", 0700)
1198 if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
1199 ChangeOwner(dir, self.UserName)
1200 return path
1201
1202 def NumberToBytes(self, i):
1203 result = []
1204 while i:
1205 result.append(chr(i&0xFF))
1206 i >>= 8
1207 result.reverse()
1208 return ''.join(result)
1209
1210 def BitsToString(self, a):
1211 index=7
1212 s = ""
1213 c = 0
1214 for bit in a:
1215 c = c | (bit << index)
1216 index = index - 1
1217 if index == -1:
1218 s = s + struct.pack('>B', c)
1219 c = 0
1220 index = 7
1221 return s
1222
1223 def OpensslToSsh(self, file):
1224 from pyasn1.codec.der import decoder as der_decoder
1225 try:
1226 f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
1227 k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
1228 n=k[0]
1229 e=k[1]
1230 keydata=""
1231 keydata += struct.pack('>I',len("ssh-rsa"))
1232 keydata += "ssh-rsa"
1233 keydata += struct.pack('>I',len(self.NumberToBytes(e)))
1234 keydata += self.NumberToBytes(e)
1235 keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
1236 keydata += "\0"
1237 keydata += self.NumberToBytes(n)
1238 except Exception, e:
1239 print("OpensslToSsh: Exception " + str(e))
1240 return None
1241 return "ssh-rsa " + base64.b64encode(keydata) + "\n"
1242
1243 def Process(self):
1244 error = None
1245 WaAgent.EnvMonitor.SetHostName(self.ComputerName)
1246 if self.DisableSshPasswordAuthentication:
1247 filepath = "/etc/ssh/sshd_config"
1248 # Disable RFC 4252 and RFC 4256 authentication schemes.
1249 ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
1250 (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
1251 GetFileContents(filepath).split('\n'))) + "PasswordAuthentication no\nChallengeResponseAuthentication no\n")
1252 Log("Disabled SSH password-based authentication methods.")
1253 if self.AdminPassword != None:
1254 os.popen("chpasswd", "w").write("root:" + self.AdminPassword + "\n")
1255 if self.UserName != None:
1256 error = CreateAccount(self.UserName, self.UserPassword, None, None)
1257 sel = os.popen("getenforce").read().startswith("Enforcing")
1258 if sel == True and IsRedHat():
1259 Run("setenforce 0")
1260 home = GetHome()
1261 for pkey in self.SshPublicKeys:
1262 if not os.path.isfile(pkey[0] + ".crt"):
1263 Error("PublicKey not found: " + pkey[0])
1264 error = "Failed to deploy public key (0x09)."
1265 continue
1266 path = self.PrepareDir(pkey[1])
1267 if path == None:
1268 Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
1269 error = "Invalid path for public key (0x03)."
1270 continue
1271 Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
1272 if IsRedHat():
1273 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + pkey[0] + ".pub")
1274 if IsUbuntu():
1275 # Only supported in new SSH releases
1276 Run("ssh-keygen -i -m PKCS8 -f " + pkey[0] + ".pub >> " + path)
1277 else:
1278 SshPubKey = self.OpensslToSsh(pkey[0] + ".pub")
1279 if SshPubKey != None:
1280 AppendFileContents(path, SshPubKey)
1281 else:
1282 Error("Failed: " + pkey[0] + ".crt -> " + path)
1283 error = "Failed to deploy public key (0x04)."
1284 if IsRedHat():
1285 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
1286 if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
1287 ChangeOwner(path, self.UserName)
1288 for keyp in self.SshKeyPairs:
1289 if not os.path.isfile(keyp[0] + ".prv"):
1290 Error("KeyPair not found: " + keyp[0])
1291 error = "Failed to deploy key pair (0x0A)."
1292 continue
1293 path = self.PrepareDir(keyp[1])
1294 if path == None:
1295 Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
1296 error = "Invalid path for key pair (0x05)."
1297 continue
1298 SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
1299 os.chmod(path, 0600)
1300 Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
1301 if IsRedHat():
1302 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
1303 Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path + ".pub")
1304 if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
1305 ChangeOwner(path, self.UserName)
1306 ChangeOwner(path + ".pub", self.UserName)
1307 if sel == True and IsRedHat():
1308 Run("setenforce 1")
1309 while not WaAgent.EnvMonitor.IsNamePublished():
1310 time.sleep(1)
1311 ReloadSshd()
1312 return error
1313
1314def UpdateAndPublishHostNameCommon(name):
1315 # RedHat
1316 if IsRedHat():
1317 filepath = "/etc/sysconfig/network"
1318 if os.path.isfile(filepath):
1319 ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n"
1320 + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n'))))
1321
1322 for ethernetInterface in PossibleEthernetInterfaces:
1323 filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
1324 if os.path.isfile(filepath):
1325 ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
1326 + "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
1327
1328 # Debian
1329 if IsDebian():
1330 SetFileContents("/etc/hostname", name)
1331
1332 for filepath in EtcDhcpClientConfFiles:
1333 if os.path.isfile(filepath):
1334 ReplaceFileContentsAtomic(filepath, "send host-name \"" + name + "\";\n"
1335 + "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents(filepath).split('\n'))))
1336
1337 # Suse
1338 if IsSuse():
1339 SetFileContents("/etc/HOSTNAME", name)
1340
1341class Agent(Util):
1342 def __init__(self):
1343 self.GoalState = None
1344 self.Endpoint = None
1345 self.LoadBalancerProbeServer = None
1346 self.HealthReportCounter = 0
1347 self.TransportCert = ""
1348 self.EnvMonitor = None
1349 self.SendData = None
1350 self.DhcpResponse = None
1351
1352 def CheckVersions(self):
1353 #<?xml version="1.0" encoding="utf-8"?>
1354 #<Versions>
1355 # <Preferred>
1356 # <Version>2010-12-15</Version>
1357 # </Preferred>
1358 # <Supported>
1359 # <Version>2010-12-15</Version>
1360 # <Version>2010-28-10</Version>
1361 # </Supported>
1362 #</Versions>
1363 global ProtocolVersion
1364 protocolVersionSeen = False
1365 node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
1366 if node.localName != "Versions":
1367 Error("CheckVersions: root not Versions")
1368 return False
1369 for a in node.childNodes:
1370 if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
1371 for b in a.childNodes:
1372 if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
1373 v = GetNodeTextData(b)
1374 LogIfVerbose("Fabric supported wire protocol version: " + v)
1375 if v == ProtocolVersion:
1376 protocolVersionSeen = True
1377 if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
1378 v = GetNodeTextData(a.getElementsByTagName("Version")[0])
1379 LogIfVerbose("Fabric preferred wire protocol version: " + v)
1380 if ProtocolVersion < v:
1381 Warn("Newer wire protocol version detected. Please consider updating waagent.")
1382 if not protocolVersionSeen:
1383 Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
1384 ProtocolVersion = "2011-08-31"
1385 Log("Negotiated wire protocol version: " + ProtocolVersion)
1386 return True
1387
1388 def Unpack(self, buffer, offset, range):
1389 result = 0
1390 for i in range:
1391 result = (result << 8) | Ord(buffer[offset + i])
1392 return result
1393
1394 def UnpackLittleEndian(self, buffer, offset, length):
1395 return self.Unpack(buffer, offset, range(length - 1, -1, -1))
1396
1397 def UnpackBigEndian(self, buffer, offset, length):
1398 return self.Unpack(buffer, offset, range(0, length))
1399
1400 def HexDump3(self, buffer, offset, length):
1401 return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
1402
1403 def HexDump2(self, buffer):
1404 return self.HexDump3(buffer, 0, len(buffer))
1405
1406 def BuildDhcpRequest(self):
1407 #
1408 # typedef struct _DHCP {
1409 # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
1410 # UINT8 HardwareAddressType; /* htype: ethernet */
1411 # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
1412 # UINT8 Hops; /* hops: 0 */
1413 # UINT8 TransactionID[4]; /* xid: random */
1414 # UINT8 Seconds[2]; /* secs: 0 */
1415 # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
1416 # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
1417 # UINT8 YourIpAddress[4]; /* yiaddr: 0 */
1418 # UINT8 ServerIpAddress[4]; /* siaddr: 0 */
1419 # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
1420 # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */
1421 # UINT8 ServerName[64]; /* sname: 0 */
1422 # UINT8 BootFileName[128]; /* file: 0 */
1423 # UINT8 MagicCookie[4]; /* 99 130 83 99 */
1424 # /* 0x63 0x82 0x53 0x63 */
1425 # /* options -- hard code ours */
1426 #
1427 # UINT8 MessageTypeCode; /* 53 */
1428 # UINT8 MessageTypeLength; /* 1 */
1429 # UINT8 MessageType; /* 1 for DISCOVER */
1430 # UINT8 End; /* 255 */
1431 # } DHCP;
1432 #
1433
1434 # tuple of 244 zeros
1435 # (struct.pack_into would be good here, but requires Python 2.5)
1436 sendData = [0] * 244
1437
1438 transactionID = os.urandom(4)
1439 macAddress = GetMacAddress()
1440
1441 # Opcode = 1
1442 # HardwareAddressType = 1 (ethernet/MAC)
1443 # HardwareAddressLength = 6 (ethernet/MAC/48 bits)
1444 for a in range(0, 3):
1445 sendData[a] = [1, 1, 6][a]
1446
1447 # fill in transaction id (random number to ensure response matches request)
1448 for a in range(0, 4):
1449 sendData[4 + a] = Ord(transactionID[a])
1450
1451 LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
1452
1453 # fill in ClientHardwareAddress
1454 for a in range(0, 6):
1455 sendData[0x1C + a] = Ord(macAddress[a])
1456
1457 # DHCP Magic Cookie: 99, 130, 83, 99
1458 # MessageTypeCode = 53 DHCP Message Type
1459 # MessageTypeLength = 1
1460 # MessageType = DHCPDISCOVER
1461 # End = 255 DHCP_END
1462 for a in range(0, 8):
1463 sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
1464 return array.array("c", map(chr, sendData))
1465
1466 def IntegerToIpAddressV4String(self, a):
1467 return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
1468
1469 def RouteAdd(self, net, mask, gateway):
1470 if IsWindows():
1471 return
1472 net = self.IntegerToIpAddressV4String(net)
1473 mask = self.IntegerToIpAddressV4String(mask)
1474 gateway = self.IntegerToIpAddressV4String(gateway)
1475 Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway)
1476
1477 def HandleDhcpResponse(self, sendData, receiveBuffer):
1478 LogIfVerbose("HandleDhcpResponse")
1479 bytesReceived = len(receiveBuffer)
1480 if bytesReceived < 0xF6:
1481 Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
1482 return None
1483
1484 LogIfVerbose("BytesReceived: " + hex(bytesReceived))
1485 LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
1486
1487 # check transactionId, cookie, MAC address
1488 # cookie should never mismatch
1489 # transactionId and MAC address may mismatch if we see a response meant from another machine
1490
1491 for offsets in [range(4, 4 + 4), range(0x1C, 0x1C + 6), range(0xEC, 0xEC + 4)]:
1492 for offset in offsets:
1493 sentByte = Ord(sendData[offset])
1494 receivedByte = Ord(receiveBuffer[offset])
1495 if sentByte != receivedByte:
1496 LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
1497 LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
1498 LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
1499 LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
1500 LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
1501 LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
1502 LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
1503 return None
1504 endpoint = None
1505
1506 #
1507 # Walk all the returned options, parsing out what we need, ignoring the others.
1508 # We need the custom option 245 to find the the endpoint we talk to,
1509 # as well as, to handle some Linux DHCP client incompatibilities,
1510 # options 3 for default gateway and 249 for routes. And 255 is end.
1511 #
1512
1513 i = 0xF0 # offset to first option
1514 while i < bytesReceived:
1515 option = Ord(receiveBuffer[i])
1516 length = 0
1517 if (i + 1) < bytesReceived:
1518 length = Ord(receiveBuffer[i + 1])
1519 LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
1520 if option == 255:
1521 LogIfVerbose("DHCP packet ended at offset " + hex(i))
1522 break
1523 elif option == 249:
1524 # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
1525 LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
1526 if length < 5:
1527 Error("Data too small for option " + option)
1528 j = i + 2
1529 while j < (i + length + 2):
1530 maskLengthBits = Ord(receiveBuffer[j])
1531 maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
1532 mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
1533 j += 1
1534 net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
1535 net <<= (32 - maskLengthBytes * 8)
1536 net &= mask
1537 j += maskLengthBytes
1538 gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
1539 j += 4
1540 self.RouteAdd(net, mask, gateway)
1541 if j != (i + length + 2):
1542 Error("HandleDhcpResponse: Unable to parse routes")
1543 elif option == 3 or option == 245:
1544 if i + 5 < bytesReceived:
1545 if length != 4:
1546 Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
1547 return None
1548 gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
1549 IpAddress = self.IntegerToIpAddressV4String(gateway)
1550 if option == 3:
1551 self.RouteAdd(0, 0, gateway)
1552 name = "DefaultGateway"
1553 else:
1554 endpoint = IpAddress
1555 name = "Windows Azure wire protocol endpoint"
1556 LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
1557 else:
1558 Error("HandleDhcpResponse: Data too small for option " + option)
1559 else:
1560 LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
1561 i += length + 2
1562 return endpoint
1563
1564 def DoDhcpWork(self):
1565 #
1566 # Discover the wire server via DHCP option 245.
1567 # And workaround incompatibility with Windows Azure DHCP servers.
1568 #
1569 ShortSleep = False # Sleep 1 second before retrying DHCP queries.
1570
1571 if not IsWindows():
1572 Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT")
1573 Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT")
1574
1575 sleepDurations = [0, 5, 10, 30, 60, 60, 60, 60]
1576 maxRetry = len(sleepDurations)
1577 lastTry = (maxRetry - 1)
1578 for retry in range(0, maxRetry):
1579 try:
1580 strRetry = str(retry)
1581 prefix = "DoDhcpWork: try=" + strRetry
1582 LogIfVerbose(prefix)
1583 sendData = self.BuildDhcpRequest()
1584 LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
1585 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
1586 sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
1587 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1588 if IsSuse():
1589 # This is required because sending after binding to 0.0.0.0 fails with
1590 # network unreachable when the default gateway is not set up.
1591 sock.bind((GetIpv4Address(), 68))
1592 else:
1593 sock.bind(("0.0.0.0", 68))
1594 sock.sendto(sendData, ("<broadcast>", 67))
1595 receiveBuffer = sock.recv(1024)
1596 sock.close()
1597 endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
1598 if endpoint == None:
1599 LogIfVerbose("DoDhcpWork: No endpoint found")
1600 if endpoint != None or retry == lastTry:
1601 if endpoint != None:
1602 self.SendData = sendData
1603 self.DhcpResponse = receiveBuffer
1604 if retry == lastTry:
1605 LogIfVerbose("DoDhcpWork: try=" + strRetry)
1606 return endpoint
1607 sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
1608 LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
1609 time.sleep(sleepDuration)
1610 except Exception, e:
1611 ErrorWithPrefix(prefix, str(e))
1612 ErrorWithPrefix(prefix, traceback.format_exc())
1613 return None
1614
1615 def UpdateAndPublishHostName(self, name):
1616 # Set hostname locally and publish to iDNS
1617 Log("Setting host name: " + name)
1618 UpdateAndPublishHostNameCommon(name)
1619 for ethernetInterface in PossibleEthernetInterfaces:
1620 if IsSuse():
1621 Run("ifrenew " + ethernetInterface)
1622 else:
1623 Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface)
1624 self.RestoreRoutes()
1625
1626 def RestoreRoutes(self):
1627 if self.SendData != None and self.DhcpResponse != None:
1628 self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
1629
1630 def UpdateGoalState(self):
1631 goalStateXml = None
1632 maxRetry = 9
1633 log = NoLog
1634 for retry in range(1, maxRetry + 1):
1635 strRetry = str(retry)
1636 log("retry UpdateGoalState,retry=" + strRetry)
1637 goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
1638 if goalStateXml != None:
1639 break
1640 log = Log
1641 time.sleep(retry)
1642 if not goalStateXml:
1643 Error("UpdateGoalState failed.")
1644 return
1645 Log("Retrieved GoalState from Windows Azure Fabric.")
1646 self.GoalState = GoalState(self).Parse(goalStateXml)
1647 return self.GoalState
1648
1649 def ReportReady(self):
1650 counter = (self.HealthReportCounter + 1) % 1000000
1651 self.HealthReportCounter = counter
1652 healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
1653 + self.GoalState.Incarnation
1654 + "</GoalStateIncarnation><Container><ContainerId>"
1655 + self.GoalState.ContainerId
1656 + "</ContainerId><RoleInstanceList><Role><InstanceId>"
1657 + self.GoalState.RoleInstanceId
1658 + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>")
1659 a = self.HttpPost("/machine?comp=health", healthReport)
1660 if a != None:
1661 return a.getheader("x-ms-latest-goal-state-incarnation-number")
1662 return None
1663
1664 def ReportNotReady(self, status, desc):
1665 healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
1666 + self.GoalState.Incarnation
1667 + "</GoalStateIncarnation><Container><ContainerId>"
1668 + self.GoalState.ContainerId
1669 + "</ContainerId><RoleInstanceList><Role><InstanceId>"
1670 + self.GoalState.RoleInstanceId
1671 + "</InstanceId><Health><State>NotReady</State>"
1672 + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>"
1673 + "</Health></Role></RoleInstanceList></Container></Health>")
1674 a = self.HttpPost("/machine?comp=health", healthReport)
1675 if a != None:
1676 return a.getheader("x-ms-latest-goal-state-incarnation-number")
1677 return None
1678
1679 def ReportRoleProperties(self, thumbprint):
1680 roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>"
1681 + "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>"
1682 + "<RoleInstances><RoleInstance>"
1683 + "<Id>" + self.GoalState.RoleInstanceId + "</Id>"
1684 + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>"
1685 + "</RoleInstance></RoleInstances></Container></RoleProperties>")
1686 a = self.HttpPost("/machine?comp=roleProperties", roleProperties)
1687 Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
1688 return a
1689
1690 def LoadBalancerProbeServer_Shutdown(self):
1691 if self.LoadBalancerProbeServer != None:
1692 self.LoadBalancerProbeServer.shutdown()
1693 self.LoadBalancerProbeServer = None
1694
1695 def GenerateTransportCert(self):
1696 Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
1697 cert = ""
1698 for line in GetFileContents("TransportCert.pem").split('\n'):
1699 if not "CERTIFICATE" in line:
1700 cert += line.rstrip()
1701 return cert
1702
1703 def Provision(self):
1704 if IsWindows():
1705 Log("Skipping Provision on Windows")
1706 return
1707 enabled = Config.get("Provisioning.Enabled")
1708 if enabled != None and enabled.lower().startswith("n"):
1709 return
1710 Log("Provisioning image started.")
1711 type = Config.get("Provisioning.SshHostKeyPairType")
1712 if type == None:
1713 type = "rsa"
1714 regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
1715 if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
1716 Run("rm -f /etc/ssh/ssh_host_*key*")
1717 Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key")
1718 ReloadSshd()
1719 SetFileContents(LibDir + "/provisioned", "")
1720 dvd = "/dev/hdc"
1721 if os.path.exists("/dev/sr0"):
1722 dvd = "/dev/sr0"
1723 if Run("fdisk -l " + dvd + " | grep Disk"):
1724 return
1725 CreateDir("/mnt/cdrom/secure", "root", 0700)
1726 if Run("mount " + dvd + " /mnt/cdrom/secure"):
1727 Error("Unable to provision: Failed to mount DVD.")
1728 return "Failed to retrieve provisioning data (0x01)."
1729 if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
1730 Error("Unable to provision: Missing ovf-env.xml on DVD.")
1731 return "Failed to retrieve provisioning data (0x02)."
1732 ovfxml = GetFileContents("/mnt/cdrom/secure/ovf-env.xml")
1733 SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml))
1734 Run("umount /mnt/cdrom/secure")
1735 error = None
1736 if ovfxml != None:
1737 Log("Provisioning image using OVF settings in the DVD.")
1738 ovfobj = OvfEnv().Parse(ovfxml)
1739 if ovfobj != None:
1740 error = ovfobj.Process()
1741 # This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml
1742 fingerprint = os.popen("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub").read().rstrip().split()[1].replace(':','')
1743 self.ReportRoleProperties(fingerprint)
1744 delRootPass = Config.get("Provisioning.DeleteRootPassword")
1745 if delRootPass != None and delRootPass.lower().startswith("y"):
1746 DeleteRootPassword()
1747 Log("Provisioning image completed.")
1748 return error
1749
1750 def Run(self):
1751 if IsLinux():
1752 SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
1753
1754 if GetIpv4Address() == None:
1755 Log("Waiting for network.")
1756 while(GetIpv4Address() == None):
1757 time.sleep(10)
1758
1759 Log("IPv4 address: " + GetIpv4Address())
1760 Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in GetMacAddress()]))
1761
1762 # Consume Entropy in ACPI table provided by Hyper-V
1763 try:
1764 SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
1765 except:
1766 pass
1767
1768 Log("Probing for Windows Azure environment.")
1769 self.Endpoint = self.DoDhcpWork()
1770
1771 if self.Endpoint == None:
1772 Log("Windows Azure environment not detected.")
1773 while True:
1774 time.sleep(60)
1775
1776 Log("Discovered Windows Azure endpoint: " + self.Endpoint)
1777 if not self.CheckVersions():
1778 Error("Agent.CheckVersions failed")
1779 sys.exit(1)
1780
1781 self.EnvMonitor = EnvMonitor()
1782
1783 # Set SCSI timeout on root device
1784 try:
1785 timeout = Config.get("OS.RootDeviceScsiTimeout")
1786 if timeout != None:
1787 SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout)
1788 except:
1789 pass
1790
1791 global Openssl
1792 Openssl = Config.get("OS.OpensslPath")
1793 if Openssl == None:
1794 Openssl = "openssl"
1795
1796 self.TransportCert = self.GenerateTransportCert()
1797
1798 incarnation = None # goalStateIncarnationFromHealthReport
1799 currentPort = None # loadBalancerProbePort
1800 goalState = None # self.GoalState, instance of GoalState
1801 provisioned = os.path.exists(LibDir + "/provisioned")
1802 program = Config.get("Role.StateConsumer")
1803 provisionError = None
1804 while True:
1805 if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
1806 goalState = self.UpdateGoalState()
1807
1808 if provisioned == False:
1809 self.ReportNotReady("Provisioning", "Starting")
1810
1811 goalState.Process()
1812
1813 if provisioned == False:
1814 provisionError = self.Provision()
1815 provisioned = True
1816
1817 #
1818 # only one port supported
1819 # restart server if new port is different than old port
1820 # stop server if no longer a port
1821 #
1822 goalPort = goalState.LoadBalancerProbePort
1823 if currentPort != goalPort:
1824 self.LoadBalancerProbeServer_Shutdown()
1825 currentPort = goalPort
1826 if currentPort != None:
1827 self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)
1828
1829 if program != None and DiskActivated == True:
1830 os.spawnl(os.P_NOWAIT, program, program, "Ready")
1831 program = None
1832
1833 if goalState.ExpectedState == "Stopped":
1834 program = Config.get("Role.StateConsumer")
1835 if program != None:
1836 Run(program + " Shutdown")
1837 self.EnvMonitor.shutdown()
1838 self.LoadBalancerProbeServer_Shutdown()
1839 command = ["/sbin/shutdown -hP now", "shutdown /s /t 5"][IsWindows()]
1840 Run(command)
1841 return
1842
1843 sleepToReduceAccessDenied = 3
1844 time.sleep(sleepToReduceAccessDenied)
1845 i = None
1846 if provisionError != None:
1847 i = self.ReportNotReady("ProvisioningFailed", provisionError)
1848 else:
1849 i = self.ReportReady()
1850 if i != None:
1851 incarnation = i
1852 time.sleep(25 - sleepToReduceAccessDenied)
1853
1854Init_Suse = """\
1855#! /bin/sh
1856
1857### BEGIN INIT INFO
1858# Provides: WindowsAzureLinuxAgent
1859# Required-Start: $network sshd
1860# Required-Stop: $network sshd
1861# Default-Start: 3 5
1862# Default-Stop: 0 1 2 6
1863# Description: Start the WindowsAzureLinuxAgent
1864### END INIT INFO
1865
1866WAZD_BIN=/usr/sbin/waagent
1867test -x $WAZD_BIN || exit 5
1868
1869case "$1" in
1870 start)
1871 echo "Starting WindowsAzureLinuxAgent"
1872 ## Start daemon with startproc(8). If this fails
1873 ## the echo return value is set appropriate.
1874
1875 startproc -f $WAZD_BIN -daemon
1876 exit $?
1877 ;;
1878 stop)
1879 echo "Shutting down WindowsAzureLinuxAgent"
1880 ## Stop daemon with killproc(8) and if this fails
1881 ## set echo the echo return value.
1882
1883 killproc -p /var/run/waagent.pid $WAZD_BIN
1884 exit $?
1885 ;;
1886 try-restart)
1887 ## Stop the service and if this succeeds (i.e. the
1888 ## service was running before), start it again.
1889 $0 status >/dev/null && $0 restart
1890 ;;
1891 restart)
1892 ## Stop the service and regardless of whether it was
1893 ## running or not, start it again.
1894 $0 stop
1895 $0 start
1896 ;;
1897 force-reload|reload)
1898 ;;
1899 status)
1900 echo -n "Checking for service WindowsAzureLinuxAgent "
1901 ## Check status with checkproc(8), if process is running
1902 ## checkproc will return with exit status 0.
1903
1904 checkproc -p $WAZD_PIDFILE $WAZD_BIN
1905 exit $?
1906 ;;
1907 probe)
1908 ;;
1909 *)
1910 echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
1911 exit 1
1912 ;;
1913esac
1914"""
1915
1916Init_RedHat = """\
1917#!/bin/bash
1918#
1919# Init file for WindowsAzureLinuxAgent.
1920#
1921# chkconfig: 2345 60 80
1922# description: WindowsAzureLinuxAgent
1923#
1924
1925# source function library
1926. /etc/rc.d/init.d/functions
1927
1928RETVAL=0
1929FriendlyName="WindowsAzureLinuxAgent"
1930WAZD_BIN=/usr/sbin/waagent
1931
1932start()
1933{
1934 echo -n $"Starting $FriendlyName: "
1935 $WAZD_BIN -daemon &
1936}
1937
1938stop()
1939{
1940 echo -n $"Stopping $FriendlyName: "
1941 killproc -p /var/run/waagent.pid $WAZD_BIN
1942 RETVAL=$?
1943 echo
1944 return $RETVAL
1945}
1946
1947case "$1" in
1948 start)
1949 start
1950 ;;
1951 stop)
1952 stop
1953 ;;
1954 restart)
1955 stop
1956 start
1957 ;;
1958 reload)
1959 ;;
1960 report)
1961 ;;
1962 status)
1963 status $WAZD_BIN
1964 RETVAL=$?
1965 ;;
1966 *)
1967 echo $"Usage: $0 {start|stop|restart|status}"
1968 RETVAL=1
1969esac
1970exit $RETVAL
1971"""
1972
1973Init_Ubuntu = """\
1974#walinuxagent - start Windows Azure agent
1975
1976description "walinuxagent"
1977author "Ben Howard <ben.howard@canonical.com>"
1978
1979start on (filesystem and started rsyslog)
1980
1981pre-start script
1982 if [ ! -x /usr/sbin/waagent ]; then
1983 exit 1
1984 fi
1985
1986 #Load the udf module
1987 modprobe -b udf
1988end script
1989
1990exec /usr/sbin/waagent -daemon
1991"""
1992
1993Init_Debian = """\
1994#!/bin/sh
1995### BEGIN INIT INFO
1996# Provides: WindowsAzureLinuxAgent
1997# Required-Start: $network $syslog
1998# Required-Stop: $network $syslog
1999# Should-Start: $network $syslog
2000# Should-Stop: $network $syslog
2001# Default-Start: 2 3 4 5
2002# Default-Stop: 0 1 6
2003# Short-Description: WindowsAzureLinuxAgent
2004# Description: WindowsAzureLinuxAgent
2005### END INIT INFO
2006
2007. /lib/lsb/init-functions
2008
2009OPTIONS="-daemon"
2010WAZD_BIN=/usr/sbin/waagent
2011WAZD_PID=/var/run/waagent.pid
2012
2013case "$1" in
2014 start)
2015 log_begin_msg "Starting WindowsAzureLinuxAgent..."
2016 pid=$( pidofproc $WAZD_BIN )
2017 if [ -n "$pid" ] ; then
2018 log_begin_msg "Already running."
2019 log_end_msg 0
2020 exit 0
2021 fi
2022 start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
2023 log_end_msg $?
2024 ;;
2025
2026 stop)
2027 log_begin_msg "Stopping WindowsAzureLinuxAgent..."
2028 start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
2029 ret=$?
2030 rm -f $WAZD_PID
2031 log_end_msg $ret
2032 ;;
2033 force-reload)
2034 $0 restart
2035 ;;
2036 restart)
2037 $0 stop
2038 $0 start
2039 ;;
2040 status)
2041 status_of_proc $WAZD_BIN && exit 0 || exit $?
2042 ;;
2043 *)
2044 log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
2045 exit 1
2046 ;;
2047esac
2048
2049exit 0
2050"""
2051
2052WaagentConf = """\
2053#
2054# Windows Azure Linux Agent Configuration
2055#
2056
2057Role.StateConsumer=None # Specified program is invoked with "Ready" or "Shutdown".
2058 # Shutdown will be initiated only after the program returns. Windows Azure will
2059 # power off the VM if shutdown is not completed within ?? minutes.
2060Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
2061Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
2062
2063Provisioning.Enabled=y #
2064Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
2065Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
2066Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
2067Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
2068
2069ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
2070ResourceDisk.Filesystem=ext4 #
2071ResourceDisk.MountPoint=/mnt/resource #
2072ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
2073ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
2074
2075LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
2076
2077Logs.Verbose=n #
2078
2079OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
2080OS.OpensslPath=None # If "None", the system default version is used.
2081"""
2082
2083WaagentLogrotate = """\
2084/var/log/waagent.log {
2085 monthly
2086 rotate 6
2087 notifempty
2088 missingok
2089}
2090"""
2091
2092def AddToLinuxKernelCmdline(options):
2093 if os.path.isfile("/boot/grub/menu.lst"):
2094 Run("sed -i --follow-symlinks '/kernel/s|$| " + options + " |' /boot/grub/menu.lst")
2095 filepath = "/etc/default/grub"
2096 if os.path.isfile(filepath):
2097 filecontents = GetFileContents(filepath).split('\n')
2098 current = filter(lambda a: a.startswith("GRUB_CMDLINE_LINUX"), filecontents)
2099 ReplaceFileContentsAtomic(filepath,
2100 "\n".join(filter(lambda a: not a.startswith("GRUB_CMDLINE_LINUX"), filecontents))
2101 + current[0][:-1] + " " + options + "\"\n")
2102 Run("update-grub")
2103
2104def ApplyVNUMAWorkaround():
2105 VersionParts = platform.release().replace('-', '.').split('.')
2106 if int(VersionParts[0]) > 2:
2107 return
2108 if int(VersionParts[1]) > 6:
2109 return
2110 if int(VersionParts[2]) > 37:
2111 return
2112 AddToLinuxKernelCmdline("numa=off")
2113 # TODO: This is not ideal for offline installation.
2114 print("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
2115
2116def RevertVNUMAWorkaround():
2117 print("Automatic reverting of GRUB configuration is not yet supported. Please edit by hand.")
2118
2119def Install():
2120 if IsWindows():
2121 print("ERROR: -install invalid for Windows.")
2122 return 1
2123 os.chmod(sys.argv[0], 0755)
2124 SwitchCwd()
2125 requiredDeps = [ "/sbin/route", "/sbin/shutdown" ]
2126 if IsDebian():
2127 requiredDeps += [ "/usr/sbin/update-rc.d" ]
2128 if IsSuse():
2129 requiredDeps += [ "/sbin/insserv" ]
2130 for a in requiredDeps:
2131 if not os.path.isfile(a):
2132 Error("Missing required dependency: " + a)
2133 return 1
2134 missing = False
2135 for a in [ "ssh-keygen", "useradd", "openssl", "sfdisk",
2136 "fdisk", "mkfs", "chpasswd", "sed", "grep", "sudo" ]:
2137 if Run("which " + a + " > /dev/null 2>&1"):
2138 Warn("Missing dependency: " + a)
2139 missing = True
2140 if missing == True:
2141 Warn("Please resolve missing dependencies listed for full functionality.")
2142 if UsesRpm():
2143 if not Run("rpm --quiet -q NetworkManager"):
2144 Error(GuestAgentLongName + " is not compatible with NetworkManager.")
2145 return 1
2146 if Run("rpm --quiet -q python-pyasn1"):
2147 Error(GuestAgentLongName + " requires python-pyasn1.")
2148 return 1
2149 if UsesDpkg() and Run("dpkg -l network-manager | grep -q ^un"):
2150 Error(GuestAgentLongName + " is not compatible with network-manager.")
2151 return 1
2152 for a in RulesFiles:
2153 if os.path.isfile(a):
2154 if os.path.isfile(GetLastPathElement(a)):
2155 os.remove(GetLastPathElement(a))
2156 shutil.move(a, ".")
2157 Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
2158
2159 if IsUbuntu():
2160 # Support for Ubuntu's upstart configuration
2161 filename="waagent.conf"
2162 filepath = "/etc/init/" + filename
2163 SetFileContents(filepath, Init_Ubuntu)
2164 os.chmod(filepath, 0644)
2165
2166 else:
2167 # Regular init.d configurations
2168 filename = "waagent"
2169 filepath = "/etc/init.d/" + filename
2170
2171 distro = IsRedHat() + IsDebian() * 2 + IsSuse()
2172 if distro == 0:
2173 Error("Unable to detect Linux Distribution.")
2174 return 1
2175 init = [[Init_RedHat, "chkconfig --add " + filename],
2176 [Init_Debian, "update-rc.d " + filename + " defaults"],
2177 [Init_Suse, "insserv " + filename]][distro - 1]
2178 SetFileContents(filepath, init[0])
2179 os.chmod(filepath, 0755)
2180 Run(init[1])
2181
2182 if os.path.isfile("/etc/waagent.conf"):
2183 try:
2184 os.remove("/etc/waagent.conf.old")
2185 except:
2186 pass
2187 try:
2188 os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
2189 Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
2190 except:
2191 pass
2192 SetFileContents("/etc/waagent.conf", WaagentConf)
2193 SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
2194 filepath = "/etc/ssh/sshd_config"
2195 ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
2196 a.startswith("ClientAliveInterval"),
2197 GetFileContents(filepath).split('\n'))) + "ClientAliveInterval 180\n")
2198 Log("Configured SSH client probing to keep connections alive.")
2199 ApplyVNUMAWorkaround()
2200 return 0
2201
2202def Uninstall():
2203 if IsWindows():
2204 print("ERROR: -uninstall invalid for windows, see waagent_service.exe")
2205 return 1
2206 SwitchCwd()
2207 for a in RulesFiles:
2208 if os.path.isfile(GetLastPathElement(a)):
2209 try:
2210 shutil.move(GetLastPathElement(a), a)
2211 Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
2212 except:
2213 pass
2214
2215 filepath = "/etc/init.d/"
2216 filename = "waagent"
2217
2218 if IsUbuntu():
2219 Run("stop " + filename)
2220 filepath = "/etc/init/"
2221 filename = "waagent.conf"
2222 else:
2223 a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
2224 if a == 0:
2225 Error("Unable to detect Linux Distribution.")
2226 return 1
2227 Run("service " + filename + " stop")
2228 cmd = ["chkconfig --del " + filename,
2229 "update-rc.d -f " + filename + " remove",
2230 "insserv -r " + filename][a - 1]
2231 Run(cmd)
2232
2233 for f in os.listdir(LibDir) + [filepath + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
2234 try:
2235 os.remove(f)
2236 except:
2237 pass
2238 RevertVNUMAWorkaround()
2239 return 0
2240
2241def DeleteRootPassword():
2242 SetFileContents("/etc/shadow-temp", "")
2243 os.chmod("/etc/shadow-temp", 0000)
2244 Run("(echo root:*LOCK*:14600:::::: && grep -v ^root /etc/shadow ) > /etc/shadow-temp")
2245 Run("mv -f /etc/shadow-temp /etc/shadow")
2246 Log("Root password deleted.")
2247
2248def Deprovision(force, deluser):
2249 if IsWindows():
2250 Run(os.environ["windir"] + "\\system32\\sysprep\\sysprep.exe /generalize")
2251 return 0
2252
2253 SwitchCwd()
2254 ovfxml = GetFileContents("ovf-env.xml")
2255 ovfobj = None
2256 if ovfxml != None:
2257 ovfobj = OvfEnv().Parse(ovfxml)
2258
2259 print("WARNING! The waagent service will be stopped.")
2260 print("WARNING! All SSH host key pairs will be deleted.")
2261
2262 if IsUbuntu():
2263 print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.")
2264 else:
2265 print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
2266
2267 print("WARNING! Cached DHCP leases will be deleted.")
2268
2269 delRootPass = Config.get("Provisioning.DeleteRootPassword")
2270 if delRootPass != None and delRootPass.lower().startswith("y"):
2271 print("WARNING! root password will be disabled. You will not be able to login as root.")
2272
2273 if ovfobj != None and deluser == True:
2274 print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
2275
2276 if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
2277 return 1
2278
2279 Run("service waagent stop")
2280
2281 if deluser == True:
2282 DeleteAccount(ovfobj.UserName)
2283
2284 # Remove SSH host keys
2285 regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
2286 if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
2287 Run("rm -f /etc/ssh/ssh_host_*key*")
2288
2289 # Remove root password
2290 if delRootPass != None and delRootPass.lower().startswith("y"):
2291 DeleteRootPassword()
2292
2293 # Remove distribution specific networking configuration
2294
2295 UpdateAndPublishHostNameCommon("localhost.localdomain")
2296
2297 # RedHat, Suse, Debian
2298 for a in VarLibDhcpDirectories:
2299 Run("rm -f " + a + "/*")
2300
2301 # Clear LibDir, remove nameserver and root bash history
2302 fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log" ]
2303
2304 # Ubuntu uses resolvconf, so we want to preserve the ability to use resolvconf
2305 if IsUbuntu():
2306 if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
2307 Log("resolvconf is not configured. Removing /etc/resolv.conf")
2308 fileBlackList.append('/etc/resolv.conf')
2309 else:
2310 Log("resolvconf is enabled; leaving /etc/resolv.conf intact")
2311 resolvConfD = '/etc/resolvconf/resolv.conf.d/'
2312 fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'originial' ])
2313 else:
2314 fileBlackList.append(os.listdir(LibDir) + '/etc/resolv.conf')
2315
2316 for f in os.listdir(LibDir) + fileBlackList:
2317 try:
2318 os.remove(f)
2319 except:
2320 pass
2321
2322 return 0
2323
2324def SwitchCwd():
2325 if not IsWindows():
2326 CreateDir(LibDir, "root", 0700)
2327 os.chdir(LibDir)
2328
2329def Usage():
2330 print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
2331 return 0
2332
2333if GuestAgentVersion == "":
2334 print("WARNING! This is a non-standard agent that does not include a valid version string.")
2335if IsLinux() and not DetectLinuxDistro():
2336 print("WARNING! Unable to detect Linux distribution. Some functionality may be broken.")
2337
2338if len(sys.argv) == 1:
2339 sys.exit(Usage())
2340
2341args = []
2342force = False
2343for a in sys.argv[1:]:
2344 if re.match("^([-/]*)(help|usage|\?)", a):
2345 sys.exit(Usage())
2346 elif re.match("^([-/]*)verbose", a):
2347 Verbose = True
2348 elif re.match("^([-/]*)force", a):
2349 force = True
2350 elif re.match("^([-/]*)(setup|install)", a):
2351 sys.exit(Install())
2352 elif re.match("^([-/]*)(uninstall)", a):
2353 sys.exit(Uninstall())
2354 else:
2355 args.append(a)
2356
2357Config = ConfigurationProvider()
2358
2359verbose = Config.get("Logs.Verbose")
2360if verbose != None and verbose.lower().startswith("y"):
2361 Verbose = True
2362
2363daemon = False
2364for a in args:
2365 if re.match("^([-/]*)deprovision\+user", a):
2366 sys.exit(Deprovision(force, True))
2367 elif re.match("^([-/]*)deprovision", a):
2368 sys.exit(Deprovision(force, False))
2369 elif re.match("^([-/]*)daemon", a):
2370 daemon = True
2371 elif re.match("^([-/]*)version", a):
2372 print(GuestAgentVersion + " running on " + LinuxDistro)
2373 sys.exit(0)
2374 elif re.match("^([-/]*)serialconsole", a):
2375 AddToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
2376 Log("Configured kernel to use ttyS0 as the boot console.")
2377 sys.exit(0)
2378 else:
2379 print("Invalid command line parameter:" + a)
2380 sys.exit(1)
2381
2382if daemon == False:
2383 sys.exit(Usage())
2384
2385try:
2386 SwitchCwd()
2387 Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
2388 if IsLinux():
2389 Log("Linux Distribution Detected : " + LinuxDistro)
2390 WaAgent = Agent()
2391 WaAgent.Run()
2392except Exception, e:
2393 Error(traceback.format_exc())
2394 Error("Exception: " + str(e))
2395 sys.exit(1)
23960
=== removed file '.pc/applied-patches'
--- .pc/applied-patches 2012-06-22 09:10:22 +0000
+++ .pc/applied-patches 1970-01-01 00:00:00 +0000
@@ -1,2 +0,0 @@
1000_ubuntu_init_resolvconf.patch
2001_ubuntu_agent_startup.patch
30
=== added file 'Changelog'
--- Changelog 1970-01-01 00:00:00 +0000
+++ Changelog 2012-11-28 16:02:22 +0000
@@ -0,0 +1,12 @@
1WALinuxAgent Changelog
2|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3
409 Nov 2012, WALinuxAgent 1.1
5 . Added sock.settimeout in DoDhcpWork() to properly timeout sock.recv
6 . Added missingDefaultRoute to handle routing issues when DHCP responses not
7 handled properly
8 . Added Children.append to avoid zombies
9 . Fixed ifrenew for compatibility
10 . Fixed shadow password file for correct SELinux context
11 . Minor cleanup work
12 . Added Changelog :)
013
=== modified file 'debian/changelog'
--- debian/changelog 2012-08-09 21:51:23 +0000
+++ debian/changelog 2012-11-28 16:02:22 +0000
@@ -1,3 +1,37 @@
1walinuxagent (1.1-0ubuntu2~12.10.01) UNRELEASED; urgency=low
2
3 * Backport of fix for LP: #1079897 from development release.
4
5 -- Louis Bouchard <louis.bouchard@canonical.com> Wed, 28 Nov 2012 16:51:12 +0100
6
7walinuxagent (1.1-0ubuntu2) raring; urgency=low
8
9 * Stop upgrades purging walinuxagent meta-data and configuration files
10 (LP: #1079897):
11 - d/{control,walinuxagent-data-saver.preinst}: Added
12 walinuxagent-data-saver package to ensure that agent generated data is
13 not lost on upgrade by diverting /usr/sbin/waagent during the upgrade
14 process.
15 - d/walinuxagent-data-saver.lintian-overrides: Override errors about use
16 of dpkg-divert in this package.
17 - d/control: Added Pre-Depends to walinuxagent on walinuxagent-data-saver.
18 - d/prerm: Stop calling waagent --uninstall during reconfiguration
19 and upgrade, specify files to remove manually for purge.
20 - d/postinst: Remove divert of /usr/sbin/waagent prior to completion of
21 package install.
22 * d/preinst: If upgrading from package version with unmanaged waagent upstart
23 configuration stop the agent and remove the upstart configuration.
24 * d/upstart: Tidied description in upstart job.
25
26 -- James Page <james.page@ubuntu.com> Fri, 23 Nov 2012 16:07:41 +0000
27
28walinuxagent (1.1-0ubuntu1) raring; urgency=low
29
30 * New upstream version (LP: #1078074, #1077147).
31 * Moved upstart job to be managed by packaging.
32
33 -- Ben Howard <ben.howard@ubuntu.com> Wed, 14 Nov 2012 10:59:37 -0700
34
1walinuxagent (1.0~git20120606.c16f5e9-0ubuntu2~12.04.1) precise-proposed; urgency=low35walinuxagent (1.0~git20120606.c16f5e9-0ubuntu2~12.04.1) precise-proposed; urgency=low
236
3 * Backport for enablement of Windows Azure IaaS platform in37 * Backport for enablement of Windows Azure IaaS platform in
438
=== modified file 'debian/control'
--- debian/control 2012-08-06 10:24:07 +0000
+++ debian/control 2012-11-28 16:02:22 +0000
@@ -10,6 +10,7 @@
1010
11Package: walinuxagent11Package: walinuxagent
12Architecture: i386 amd6412Architecture: i386 amd64
13Pre-Depends: walinuxagent-data-saver (= ${binary:Version})
13Depends: python (>= 2.4),14Depends: python (>= 2.4),
14 openssl (>=1.0),15 openssl (>=1.0),
15 openssh-server (>=1:5.9p1),16 openssh-server (>=1:5.9p1),
@@ -23,3 +24,12 @@
23 The Windows Azure Linux Agent supports the provisioning and running of Linux24 The Windows Azure Linux Agent supports the provisioning and running of Linux
24 VMs in the Windows Azure cloud. This package should be installed on Linux25 VMs in the Windows Azure cloud. This package should be installed on Linux
25 disk images that are built to run in the Windows Azure environment.26 disk images that are built to run in the Windows Azure environment.
27
28Package: walinuxagent-data-saver
29Architecture: i386 amd64
30Depends: ${misc:Depends}
31Description: Helper package which ensures safe upgrade for walinuxagent
32 Early versions of walinuxagent contained a bug the deleted configuration and
33 data on reconfiguration or upgrade.
34 .
35 This package is used to ensure safe upgrades.
2636
=== added file 'debian/patches/000_resolv-conf.patch'
--- debian/patches/000_resolv-conf.patch 1970-01-01 00:00:00 +0000
+++ debian/patches/000_resolv-conf.patch 2012-11-28 16:02:22 +0000
@@ -0,0 +1,32 @@
1Index: wa-new/waagent
2===================================================================
3--- wa-new.orig/waagent 2012-11-14 11:06:12.227371000 -0700
4+++ wa-new/waagent 2012-11-14 11:07:53.093401274 -0700
5@@ -2262,7 +2262,26 @@
6 Run("rm -f " + a + "/*")
7
8 # Clear LibDir, remove nameserver and root bash history
9- for f in os.listdir(LibDir) + ["/etc/resolv.conf", "/root/.bash_history", "/var/log/waagent.log"]:
10+ fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log" ]
11+
12+ if IsUbuntu():
13+ # Ubuntu uses resolv.conf by default, so removing /etc/resolv.conf will
14+ # break resolvconf. Therefore, we check to see if resolvconf is in use,
15+ # and if so, we remove the resolvconf artifacts.
16+
17+ if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
18+ Log("resolvconf is not configured. Removing /etc/resolv.conf")
19+ fileBlackList.append('/etc/resolv.conf')
20+ else:
21+ Log("resolvconf is enabled; leaving /etc/resolv.conf intact")
22+ resolvConfD = '/etc/resolvconf/resolv.conf.d/'
23+ fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'originial' ])
24+ else:
25+ fileBlackList.append(os.listdir(LibDir) + '/etc/resolv.conf')
26+
27+
28+ # Clear LibDir, remove nameserver and root bash history
29+ for f in os.listdir(LibDir) + fileBlackList:
30 try:
31 os.remove(f)
32 except:
033
=== removed file 'debian/patches/000_ubuntu_init_resolvconf.patch'
--- debian/patches/000_ubuntu_init_resolvconf.patch 2012-06-22 09:10:22 +0000
+++ debian/patches/000_ubuntu_init_resolvconf.patch 1970-01-01 00:00:00 +0000
@@ -1,153 +0,0 @@
1Description: Microsoft provided walinuxagent does not include the
2 required UFS module, is not resolvconf aware, and does not provide
3 an upstart script; this patch fixes all of that.
4Author: Ben Howard <ben.howard@ubuntu.com>
5Bug-Ubuntu: https://bugs.launchpad.net/bugs/1014864
6Forwarded: yes
7
8--- a/waagent
9+++ b/waagent
10@@ -1970,6 +1970,26 @@ esac
11 exit $RETVAL
12 """
13
14+Init_Ubuntu = """\
15+#walinuxagent - start Windows Azure agent
16+
17+description "walinuxagent"
18+author "Ben Howard <ben.howard@canonical.com>"
19+
20+start on (filesystem and started rsyslog)
21+
22+pre-start script
23+ if [ ! -x /usr/sbin/waagent ]; then
24+ exit 1
25+ fi
26+
27+ #Load the udf module
28+ modprobe -b udf
29+end script
30+
31+exec /usr/sbin/waagent -daemon
32+"""
33+
34 Init_Debian = """\
35 #!/bin/sh
36 ### BEGIN INIT INFO
37@@ -2135,18 +2155,30 @@ def Install():
38 os.remove(GetLastPathElement(a))
39 shutil.move(a, ".")
40 Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
41- filename = "waagent"
42- filepath = "/etc/init.d/" + filename
43- distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
44- if distro == 0:
45- Error("Unable to detect Linux Distribution.")
46- return 1
47- init = [[Init_RedHat, "chkconfig --add " + filename],
48- [Init_Debian, "update-rc.d " + filename + " defaults"],
49- [Init_Suse, "insserv " + filename]][distro - 1]
50- SetFileContents(filepath, init[0])
51- os.chmod(filepath, 0755)
52- Run(init[1])
53+
54+ if IsUbuntu():
55+ # Support for Ubuntu's upstart configuration
56+ filename="waagent.conf"
57+ filepath = "/etc/init/" + filename
58+ SetFileContents(filepath, Init_Ubuntu)
59+ os.chmod(filepath, 0644)
60+
61+ else:
62+ # Regular init.d configurations
63+ filename = "waagent"
64+ filepath = "/etc/init.d/" + filename
65+
66+ distro = IsRedHat() + IsDebian() * 2 + IsSuse()
67+ if distro == 0:
68+ Error("Unable to detect Linux Distribution.")
69+ return 1
70+ init = [[Init_RedHat, "chkconfig --add " + filename],
71+ [Init_Debian, "update-rc.d " + filename + " defaults"],
72+ [Init_Suse, "insserv " + filename]][distro - 1]
73+ SetFileContents(filepath, init[0])
74+ os.chmod(filepath, 0755)
75+ Run(init[1])
76+
77 if os.path.isfile("/etc/waagent.conf"):
78 try:
79 os.remove("/etc/waagent.conf.old")
80@@ -2179,17 +2211,26 @@ def Uninstall():
81 Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
82 except:
83 pass
84+
85+ filepath = "/etc/init.d/"
86 filename = "waagent"
87- a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
88- if a == 0:
89- Error("Unable to detect Linux Distribution.")
90- return 1
91- Run("service " + filename + " stop")
92- cmd = ["chkconfig --del " + filename,
93- "update-rc.d -f " + filename + " remove",
94- "insserv -r " + filename][a - 1]
95- Run(cmd)
96- for f in os.listdir(LibDir) + ["/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
97+
98+ if IsUbuntu():
99+ Run("stop " + filename)
100+ filepath = "/etc/init/"
101+ filename = "waagent.conf"
102+ else:
103+ a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
104+ if a == 0:
105+ Error("Unable to detect Linux Distribution.")
106+ return 1
107+ Run("service " + filename + " stop")
108+ cmd = ["chkconfig --del " + filename,
109+ "update-rc.d -f " + filename + " remove",
110+ "insserv -r " + filename][a - 1]
111+ Run(cmd)
112+
113+ for f in os.listdir(LibDir) + [filepath + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
114 try:
115 os.remove(f)
116 except:
117@@ -2217,7 +2258,12 @@ def Deprovision(force, deluser):
118
119 print("WARNING! The waagent service will be stopped.")
120 print("WARNING! All SSH host key pairs will be deleted.")
121- print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
122+
123+ if IsUbuntu():
124+ print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.")
125+ else:
126+ print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches