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

Proposed by Ben Howard
Status: Rejected
Rejected by: James Page
Proposed branch: lp:~darkmuggle-deactivatedaccount/ubuntu/precise/walinuxagent/lp1077148
Merge into: lp:ubuntu/precise-proposed/walinuxagent
Diff against target: 7775 lines (+2678/-4819) (has conflicts)
14 files modified
.pc/.quilt_patches (+0/-1)
.pc/.quilt_series (+0/-1)
.pc/.version (+0/-1)
.pc/000_resolv-conf.patch/waagent (+0/-2344)
.pc/000_use_package_upstart.patch/waagent (+2473/-0)
.pc/001-strip-init-d.patch/waagent (+0/-2363)
.pc/applied-patches (+0/-2)
Changelog (+13/-0)
debian/changelog (+42/-7)
debian/patches/000_resolv-conf.patch (+0/-32)
debian/patches/000_use_package_upstart.patch (+16/-0)
debian/patches/001-strip-init-d.patch (+0/-36)
debian/patches/series (+0/-2)
waagent (+134/-30)
Text conflict in debian/changelog
To merge this branch: bzr merge lp:~darkmuggle-deactivatedaccount/ubuntu/precise/walinuxagent/lp1077148
Reviewer Review Type Date Requested Status
James Page Needs Fixing
Review via email: mp+139715@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 :

Version number is incorrect:

walinuxagent (1.1-0ubuntu2~12.04.1ubuntu1) precise-proposed; urgency=high

->

walinuxagent (1.2-0ubuntu2~12.04.1) precise-proposed; urgency=high

Revision history for this message
James Page (james-page) :
review: Needs Fixing
8. By Ben Howard

Correction of version numbering

Unmerged revisions

8. By Ben Howard

Correction of version numbering

7. By Ben Howard

Upgrade to version 1.2 (LP: #1077148).

6. By Ben Howard

Upgrade base package to version 1.2

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

Subscribers

People subscribed via source and target branches

to all changes: