Merge lp:~darkmuggle-deactivatedaccount/ubuntu/quantal/walinuxagent/lp1077148 into lp:ubuntu/quantal/walinuxagent

Proposed by Ben Howard
Status: Merged
Merge reported by: James Page
Merged at revision: not available
Proposed branch: lp:~darkmuggle-deactivatedaccount/ubuntu/quantal/walinuxagent/lp1077148
Merge into: lp:ubuntu/quantal/walinuxagent
Diff against target: 8316 lines (+2824/-5015)
22 files modified
.pc/.quilt_patches (+0/-1)
.pc/.quilt_series (+0/-1)
.pc/.version (+0/-1)
.pc/000_ubuntu_init_resolvconf.patch/waagent (+0/-2335)
.pc/000_use_package_upstart.patch/waagent (+2473/-0)
.pc/001_ubuntu_agent_startup.patch/waagent (+0/-2395)
.pc/applied-patches (+0/-2)
Changelog (+25/-0)
debian/changelog (+60/-0)
debian/control (+10/-0)
debian/patches/000_ubuntu_init_resolvconf.patch (+0/-153)
debian/patches/000_use_package_upstart.patch (+16/-0)
debian/patches/001_ubuntu_agent_startup.patch (+0/-22)
debian/patches/series (+0/-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 (+162/-94)
To merge this branch: bzr merge lp:~darkmuggle-deactivatedaccount/ubuntu/quantal/walinuxagent/lp1077148
Reviewer Review Type Date Requested Status
James Page Needs Fixing
Review via email: mp+139716@code.launchpad.net

Description of the change

This package fixes the following issues:
    - Added - load ata_piix.ko module loaded if needed for CDROM device support
    - Additional logging for DoDhcpWork()
    - Update sock.recv timeout from 30 to 10 seconds
    - Fix: Linux waagent deprovision, user is not deleted properly
    - Fix: Make LBProbeResponder construction more robust
    - Fix: Agent fails to provision user with public/private key pairs
    - Fix: DHCP broadcast response not received
    - Fix: Linux agent fails to delete root user password
    - Fix: Linux agent should report error messages to Fabric when
           passed an invalid hostname.

To post a comment you must log in.
Revision history for this message
James Page (james-page) wrote :

walinuxagent (1.2-0ubuntu1~12.10.1ubuntu1) quantal-proposed; urgency=high

->

walinuxagent (1.2-0ubuntu1~12.10.1) quantal-proposed; urgency=high

review: Needs Fixing
8. By Ben Howard

Correction of version number

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: