Merge lp:~darkmuggle-deactivatedaccount/ubuntu/precise/walinuxagent/lp1077148 into lp:ubuntu/precise-proposed/walinuxagent
- Precise (12.04)
- lp1077148
- Merge into precise-proposed
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Page | Needs Fixing | ||
Review via email: mp+139715@code.launchpad.net |
Commit message
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.
James Page (james-page) wrote : | # |
James Page (james-page) : | # |
- 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
1 | === added directory '.pc' | |||
2 | === removed directory '.pc' | |||
3 | === added file '.pc/.quilt_patches' | |||
4 | --- .pc/.quilt_patches 1970-01-01 00:00:00 +0000 | |||
5 | +++ .pc/.quilt_patches 2012-12-13 16:36:21 +0000 | |||
6 | @@ -0,0 +1,1 @@ | |||
7 | 1 | debian/patches | ||
8 | 0 | 2 | ||
9 | === removed file '.pc/.quilt_patches' | |||
10 | --- .pc/.quilt_patches 2012-06-22 09:10:22 +0000 | |||
11 | +++ .pc/.quilt_patches 1970-01-01 00:00:00 +0000 | |||
12 | @@ -1,1 +0,0 @@ | |||
13 | 1 | debian/patches | ||
14 | 2 | 0 | ||
15 | === added file '.pc/.quilt_series' | |||
16 | --- .pc/.quilt_series 1970-01-01 00:00:00 +0000 | |||
17 | +++ .pc/.quilt_series 2012-12-13 16:36:21 +0000 | |||
18 | @@ -0,0 +1,1 @@ | |||
19 | 1 | series | ||
20 | 0 | 2 | ||
21 | === removed file '.pc/.quilt_series' | |||
22 | --- .pc/.quilt_series 2012-06-22 09:10:22 +0000 | |||
23 | +++ .pc/.quilt_series 1970-01-01 00:00:00 +0000 | |||
24 | @@ -1,1 +0,0 @@ | |||
25 | 1 | series | ||
26 | 2 | 0 | ||
27 | === added file '.pc/.version' | |||
28 | --- .pc/.version 1970-01-01 00:00:00 +0000 | |||
29 | +++ .pc/.version 2012-12-13 16:36:21 +0000 | |||
30 | @@ -0,0 +1,1 @@ | |||
31 | 1 | 2 | ||
32 | 0 | 2 | ||
33 | === removed file '.pc/.version' | |||
34 | --- .pc/.version 2012-06-22 09:10:22 +0000 | |||
35 | +++ .pc/.version 1970-01-01 00:00:00 +0000 | |||
36 | @@ -1,1 +0,0 @@ | |||
37 | 1 | 2 | ||
38 | 2 | 0 | ||
39 | === removed directory '.pc/000_resolv-conf.patch' | |||
40 | === removed file '.pc/000_resolv-conf.patch/waagent' | |||
41 | --- .pc/000_resolv-conf.patch/waagent 2012-11-14 10:59:37 +0000 | |||
42 | +++ .pc/000_resolv-conf.patch/waagent 1970-01-01 00:00:00 +0000 | |||
43 | @@ -1,2344 +0,0 @@ | |||
44 | 1 | #!/usr/bin/python | ||
45 | 2 | # | ||
46 | 3 | # Windows Azure Linux Agent | ||
47 | 4 | # | ||
48 | 5 | # Copyright 2012 Microsoft Corporation | ||
49 | 6 | # | ||
50 | 7 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
51 | 8 | # you may not use this file except in compliance with the License. | ||
52 | 9 | # You may obtain a copy of the License at | ||
53 | 10 | # | ||
54 | 11 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
55 | 12 | # | ||
56 | 13 | # Unless required by applicable law or agreed to in writing, software | ||
57 | 14 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
58 | 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
59 | 16 | # See the License for the specific language governing permissions and | ||
60 | 17 | # limitations under the License. | ||
61 | 18 | # | ||
62 | 19 | # Requires Python 2.4+ and Openssl 1.0+ | ||
63 | 20 | # | ||
64 | 21 | # Implements parts of RFC 2131, 1541, 1497 and | ||
65 | 22 | # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx | ||
66 | 23 | # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx | ||
67 | 24 | # | ||
68 | 25 | |||
69 | 26 | import array | ||
70 | 27 | import base64 | ||
71 | 28 | import httplib | ||
72 | 29 | import os | ||
73 | 30 | import os.path | ||
74 | 31 | import platform | ||
75 | 32 | import pwd | ||
76 | 33 | import re | ||
77 | 34 | import shutil | ||
78 | 35 | import socket | ||
79 | 36 | import SocketServer | ||
80 | 37 | import struct | ||
81 | 38 | import subprocess | ||
82 | 39 | import sys | ||
83 | 40 | import tempfile | ||
84 | 41 | import textwrap | ||
85 | 42 | import threading | ||
86 | 43 | import time | ||
87 | 44 | import traceback | ||
88 | 45 | import xml.dom.minidom | ||
89 | 46 | |||
90 | 47 | GuestAgentName = "WALinuxAgent" | ||
91 | 48 | GuestAgentLongName = "Windows Azure Linux Agent" | ||
92 | 49 | GuestAgentVersion = "WALinuxAgent-1.1" | ||
93 | 50 | ProtocolVersion = "2011-12-31" | ||
94 | 51 | |||
95 | 52 | Config = None | ||
96 | 53 | LinuxDistro = "UNKNOWN" | ||
97 | 54 | Verbose = False | ||
98 | 55 | WaAgent = None | ||
99 | 56 | DiskActivated = False | ||
100 | 57 | Openssl = "openssl" | ||
101 | 58 | Children = [] | ||
102 | 59 | |||
103 | 60 | PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"] | ||
104 | 61 | RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules", | ||
105 | 62 | "/etc/udev/rules.d/70-persistent-net.rules" ] | ||
106 | 63 | VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"] | ||
107 | 64 | EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"] | ||
108 | 65 | LibDir = "/var/lib/waagent" | ||
109 | 66 | |||
110 | 67 | # This lets us index into a string or an array of integers transparently. | ||
111 | 68 | def Ord(a): | ||
112 | 69 | if type(a) == type("a"): | ||
113 | 70 | a = ord(a) | ||
114 | 71 | return a | ||
115 | 72 | |||
116 | 73 | def IsWindows(): | ||
117 | 74 | return (platform.uname()[0] == "Windows") | ||
118 | 75 | |||
119 | 76 | def IsLinux(): | ||
120 | 77 | return (platform.uname()[0] == "Linux") | ||
121 | 78 | |||
122 | 79 | def DetectLinuxDistro(): | ||
123 | 80 | global LinuxDistro | ||
124 | 81 | if os.path.isfile("/etc/redhat-release"): | ||
125 | 82 | LinuxDistro = "RedHat" | ||
126 | 83 | return True | ||
127 | 84 | if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"): | ||
128 | 85 | LinuxDistro = "Ubuntu" | ||
129 | 86 | return True | ||
130 | 87 | if os.path.isfile("/etc/debian_version"): | ||
131 | 88 | LinuxDistro = "Debian" | ||
132 | 89 | return True | ||
133 | 90 | if os.path.isfile("/etc/SuSE-release"): | ||
134 | 91 | LinuxDistro = "Suse" | ||
135 | 92 | return True | ||
136 | 93 | return False | ||
137 | 94 | |||
138 | 95 | def IsRedHat(): | ||
139 | 96 | return "RedHat" in LinuxDistro | ||
140 | 97 | |||
141 | 98 | def IsUbuntu(): | ||
142 | 99 | return "Ubuntu" in LinuxDistro | ||
143 | 100 | |||
144 | 101 | def IsDebian(): | ||
145 | 102 | return IsUbuntu() or "Debian" in LinuxDistro | ||
146 | 103 | |||
147 | 104 | def IsSuse(): | ||
148 | 105 | return "Suse" in LinuxDistro | ||
149 | 106 | |||
150 | 107 | def UsesRpm(): | ||
151 | 108 | return IsRedHat() or IsSuse() | ||
152 | 109 | |||
153 | 110 | def UsesDpkg(): | ||
154 | 111 | return IsDebian() | ||
155 | 112 | |||
156 | 113 | def GetLastPathElement(path): | ||
157 | 114 | return path.rsplit('/', 1)[1] | ||
158 | 115 | |||
159 | 116 | def GetFileContents(filepath): | ||
160 | 117 | file = None | ||
161 | 118 | try: | ||
162 | 119 | file = open(filepath) | ||
163 | 120 | except: | ||
164 | 121 | return None | ||
165 | 122 | if file == None: | ||
166 | 123 | return None | ||
167 | 124 | try: | ||
168 | 125 | return file.read() | ||
169 | 126 | finally: | ||
170 | 127 | file.close() | ||
171 | 128 | |||
172 | 129 | def SetFileContents(filepath, contents): | ||
173 | 130 | file = open(filepath, "w") | ||
174 | 131 | try: | ||
175 | 132 | file.write(contents) | ||
176 | 133 | finally: | ||
177 | 134 | file.close() | ||
178 | 135 | |||
179 | 136 | def AppendFileContents(filepath, contents): | ||
180 | 137 | file = open(filepath, "a") | ||
181 | 138 | try: | ||
182 | 139 | file.write(contents) | ||
183 | 140 | finally: | ||
184 | 141 | file.close() | ||
185 | 142 | |||
186 | 143 | def ReplaceFileContentsAtomic(filepath, contents): | ||
187 | 144 | handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath)) | ||
188 | 145 | try: | ||
189 | 146 | os.write(handle, contents) | ||
190 | 147 | finally: | ||
191 | 148 | os.close(handle) | ||
192 | 149 | try: | ||
193 | 150 | os.rename(temp, filepath) | ||
194 | 151 | return | ||
195 | 152 | except: | ||
196 | 153 | pass | ||
197 | 154 | os.remove(filepath) | ||
198 | 155 | os.rename(temp, filepath) | ||
199 | 156 | |||
200 | 157 | def GetLineStartingWith(prefix, filepath): | ||
201 | 158 | for line in GetFileContents(filepath).split('\n'): | ||
202 | 159 | if line.startswith(prefix): | ||
203 | 160 | return line | ||
204 | 161 | return None | ||
205 | 162 | |||
206 | 163 | def Run(a): | ||
207 | 164 | LogIfVerbose(a) | ||
208 | 165 | return os.system(a) | ||
209 | 166 | |||
210 | 167 | def GetNodeTextData(a): | ||
211 | 168 | for b in a.childNodes: | ||
212 | 169 | if b.nodeType == b.TEXT_NODE: | ||
213 | 170 | return b.data | ||
214 | 171 | |||
215 | 172 | def GetHome(): | ||
216 | 173 | home = None | ||
217 | 174 | try: | ||
218 | 175 | home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip() | ||
219 | 176 | except: | ||
220 | 177 | pass | ||
221 | 178 | if (home == None) or (home.startswith("/") == False): | ||
222 | 179 | home = "/home" | ||
223 | 180 | return home | ||
224 | 181 | |||
225 | 182 | def ChangeOwner(filepath, user): | ||
226 | 183 | p = None | ||
227 | 184 | try: | ||
228 | 185 | p = pwd.getpwnam(user) | ||
229 | 186 | except: | ||
230 | 187 | pass | ||
231 | 188 | if p != None: | ||
232 | 189 | os.chown(filepath, p[2], p[3]) | ||
233 | 190 | |||
234 | 191 | def CreateDir(dirpath, user, mode): | ||
235 | 192 | try: | ||
236 | 193 | os.makedirs(dirpath, mode) | ||
237 | 194 | except: | ||
238 | 195 | pass | ||
239 | 196 | ChangeOwner(dirpath, user) | ||
240 | 197 | |||
241 | 198 | def CreateAccount(user, password, expiration, thumbprint): | ||
242 | 199 | if IsWindows(): | ||
243 | 200 | Log("Skipping CreateAccount on Windows") | ||
244 | 201 | return None | ||
245 | 202 | userentry = None | ||
246 | 203 | try: | ||
247 | 204 | userentry = pwd.getpwnam(user) | ||
248 | 205 | except: | ||
249 | 206 | pass | ||
250 | 207 | uidmin = None | ||
251 | 208 | try: | ||
252 | 209 | uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1]) | ||
253 | 210 | except: | ||
254 | 211 | pass | ||
255 | 212 | if uidmin == None: | ||
256 | 213 | uidmin = 100 | ||
257 | 214 | if userentry != None and userentry[2] < uidmin: | ||
258 | 215 | Error("CreateAccount: " + user + " is a system user. Will not set password.") | ||
259 | 216 | return "Failed to set password for system user: " + user + " (0x06)." | ||
260 | 217 | if userentry == None: | ||
261 | 218 | command = "useradd -m " + user | ||
262 | 219 | if expiration != None: | ||
263 | 220 | command += " -e " + expiration.split('.')[0] | ||
264 | 221 | if Run(command): | ||
265 | 222 | Error("Failed to create user account: " + user) | ||
266 | 223 | return "Failed to create user account: " + user + " (0x07)." | ||
267 | 224 | else: | ||
268 | 225 | Log("CreateAccount: " + user + " already exists. Will update password.") | ||
269 | 226 | if password != None: | ||
270 | 227 | os.popen("chpasswd", "w").write(user + ":" + password + "\n") | ||
271 | 228 | try: | ||
272 | 229 | if password == None: | ||
273 | 230 | SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n") | ||
274 | 231 | else: | ||
275 | 232 | SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n") | ||
276 | 233 | os.chmod("/etc/sudoers.d/waagent", 0440) | ||
277 | 234 | except: | ||
278 | 235 | Error("CreateAccount: Failed to configure sudo access for user.") | ||
279 | 236 | return "Failed to configure sudo privileges (0x08)." | ||
280 | 237 | home = GetHome() | ||
281 | 238 | if thumbprint != None: | ||
282 | 239 | dir = home + "/" + user + "/.ssh" | ||
283 | 240 | CreateDir(dir, user, 0700) | ||
284 | 241 | pub = dir + "/id_rsa.pub" | ||
285 | 242 | prv = dir + "/id_rsa" | ||
286 | 243 | Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub) | ||
287 | 244 | SetFileContents(prv, GetFileContents(thumbprint + ".prv")) | ||
288 | 245 | for f in [pub, prv]: | ||
289 | 246 | os.chmod(f, 0600) | ||
290 | 247 | ChangeOwner(f, user) | ||
291 | 248 | SetFileContents(dir + "/authorized_keys", GetFileContents(pub)) | ||
292 | 249 | ChangeOwner(dir + "/authorized_keys", user) | ||
293 | 250 | Log("Created user account: " + user) | ||
294 | 251 | return None | ||
295 | 252 | |||
296 | 253 | def DeleteAccount(user): | ||
297 | 254 | if IsWindows(): | ||
298 | 255 | Log("Skipping DeleteAccount on Windows") | ||
299 | 256 | return | ||
300 | 257 | userentry = None | ||
301 | 258 | try: | ||
302 | 259 | userentry = pwd.getpwnam(user) | ||
303 | 260 | except: | ||
304 | 261 | pass | ||
305 | 262 | if userentry == None: | ||
306 | 263 | Error("DeleteAccount: " + user + " not found.") | ||
307 | 264 | return | ||
308 | 265 | uidmin = None | ||
309 | 266 | try: | ||
310 | 267 | uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1]) | ||
311 | 268 | except: | ||
312 | 269 | pass | ||
313 | 270 | if uidmin == None: | ||
314 | 271 | uidmin = 100 | ||
315 | 272 | if userentry[2] < uidmin: | ||
316 | 273 | Error("DeleteAccount: " + user + " is a system user. Will not delete account.") | ||
317 | 274 | return | ||
318 | 275 | Run("userdel -f -r " + user) | ||
319 | 276 | try: | ||
320 | 277 | os.remove("/etc/sudoers.d/waagent") | ||
321 | 278 | except: | ||
322 | 279 | pass | ||
323 | 280 | return | ||
324 | 281 | |||
325 | 282 | def ReloadSshd(): | ||
326 | 283 | name = None | ||
327 | 284 | if IsRedHat() or IsSuse(): | ||
328 | 285 | name = "sshd" | ||
329 | 286 | if IsDebian(): | ||
330 | 287 | name = "ssh" | ||
331 | 288 | if name == None: | ||
332 | 289 | return | ||
333 | 290 | if not Run("service " + name + " status | grep running"): | ||
334 | 291 | Run("service " + name + " reload") | ||
335 | 292 | |||
336 | 293 | def IsInRangeInclusive(a, low, high): | ||
337 | 294 | return (a >= low and a <= high) | ||
338 | 295 | |||
339 | 296 | def IsPrintable(ch): | ||
340 | 297 | return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9')) | ||
341 | 298 | |||
342 | 299 | def HexDump(buffer, size): | ||
343 | 300 | if size < 0: | ||
344 | 301 | size = len(buffer) | ||
345 | 302 | result = "" | ||
346 | 303 | for i in range(0, size): | ||
347 | 304 | if (i % 16) == 0: | ||
348 | 305 | result += "%06X: " % i | ||
349 | 306 | byte = struct.unpack("B", buffer[i])[0] | ||
350 | 307 | result += "%02X " % byte | ||
351 | 308 | if (i & 15) == 7: | ||
352 | 309 | result += " " | ||
353 | 310 | if ((i + 1) % 16) == 0 or (i + 1) == size: | ||
354 | 311 | j = i | ||
355 | 312 | while ((j + 1) % 16) != 0: | ||
356 | 313 | result += " " | ||
357 | 314 | if (j & 7) == 7: | ||
358 | 315 | result += " " | ||
359 | 316 | j += 1 | ||
360 | 317 | result += " " | ||
361 | 318 | for j in range(i - (i % 16), i + 1): | ||
362 | 319 | byte = struct.unpack("B", buffer[j])[0] | ||
363 | 320 | k = '.' | ||
364 | 321 | if IsPrintable(byte): | ||
365 | 322 | k = chr(byte) | ||
366 | 323 | result += k | ||
367 | 324 | if (i + 1) != size: | ||
368 | 325 | result += "\n" | ||
369 | 326 | return result | ||
370 | 327 | |||
371 | 328 | def ThrottleLog(counter): | ||
372 | 329 | # Log everything up to 10, every 10 up to 100, then every 100. | ||
373 | 330 | return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0) | ||
374 | 331 | |||
375 | 332 | def Logger(): | ||
376 | 333 | class T(object): | ||
377 | 334 | def __init__(self): | ||
378 | 335 | self.File = None | ||
379 | 336 | |||
380 | 337 | self = T() | ||
381 | 338 | |||
382 | 339 | def LogToFile(message): | ||
383 | 340 | FilePath = ["/var/log/waagent.log", "waagent.log"][IsWindows()] | ||
384 | 341 | if not os.path.isfile(FilePath) and self.File != None: | ||
385 | 342 | self.File.close() | ||
386 | 343 | self.File = None | ||
387 | 344 | if self.File == None: | ||
388 | 345 | self.File = open(FilePath, "a") | ||
389 | 346 | self.File.write(message + "\n") | ||
390 | 347 | self.File.flush() | ||
391 | 348 | |||
392 | 349 | def Log(message): | ||
393 | 350 | LogWithPrefix("", message) | ||
394 | 351 | |||
395 | 352 | def LogWithPrefix(prefix, message): | ||
396 | 353 | t = time.localtime() | ||
397 | 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) | ||
398 | 355 | t += prefix | ||
399 | 356 | for line in message.split('\n'): | ||
400 | 357 | line = t + line | ||
401 | 358 | print(line) | ||
402 | 359 | LogToFile(line) | ||
403 | 360 | |||
404 | 361 | return Log, LogWithPrefix | ||
405 | 362 | |||
406 | 363 | Log, LogWithPrefix = Logger() | ||
407 | 364 | |||
408 | 365 | def NoLog(message): | ||
409 | 366 | pass | ||
410 | 367 | |||
411 | 368 | def LogIfVerbose(message): | ||
412 | 369 | if Verbose == True: | ||
413 | 370 | Log(message) | ||
414 | 371 | |||
415 | 372 | def LogWithPrefixIfVerbose(prefix, message): | ||
416 | 373 | if Verbose == True: | ||
417 | 374 | LogWithPrefix(prefix, message) | ||
418 | 375 | |||
419 | 376 | def Warn(message): | ||
420 | 377 | LogWithPrefix("WARNING:", message) | ||
421 | 378 | |||
422 | 379 | def Error(message): | ||
423 | 380 | LogWithPrefix("ERROR:", message) | ||
424 | 381 | |||
425 | 382 | def ErrorWithPrefix(prefix, message): | ||
426 | 383 | LogWithPrefix("ERROR:" + prefix, message) | ||
427 | 384 | |||
428 | 385 | def Linux_ioctl_GetIpv4Address(ifname): | ||
429 | 386 | import fcntl | ||
430 | 387 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
431 | 388 | return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24]) | ||
432 | 389 | |||
433 | 390 | def Linux_ioctl_GetInterfaceMac(ifname): | ||
434 | 391 | import fcntl | ||
435 | 392 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
436 | 393 | info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15])) | ||
437 | 394 | return ''.join(['%02X' % Ord(char) for char in info[18:24]]) | ||
438 | 395 | |||
439 | 396 | def GetIpv4Address(): | ||
440 | 397 | if IsLinux(): | ||
441 | 398 | for ifname in PossibleEthernetInterfaces: | ||
442 | 399 | try: | ||
443 | 400 | return Linux_ioctl_GetIpv4Address(ifname) | ||
444 | 401 | except IOError, e: | ||
445 | 402 | pass | ||
446 | 403 | else: | ||
447 | 404 | try: | ||
448 | 405 | return socket.gethostbyname(socket.gethostname()) | ||
449 | 406 | except Exception, e: | ||
450 | 407 | ErrorWithPrefix("GetIpv4Address:", str(e)) | ||
451 | 408 | ErrorWithPrefix("GetIpv4Address:", traceback.format_exc()) | ||
452 | 409 | |||
453 | 410 | def HexStringToByteArray(a): | ||
454 | 411 | b = "" | ||
455 | 412 | for c in range(0, len(a) / 2): | ||
456 | 413 | b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16)) | ||
457 | 414 | return b | ||
458 | 415 | |||
459 | 416 | def GetMacAddress(): | ||
460 | 417 | if IsWindows(): | ||
461 | 418 | # Windows: Physical Address. . . . . . . . . : 00-15-17-79-00-7F\n | ||
462 | 419 | a = "ipconfig /all | findstr /c:\"Physical Address\" | findstr /v \"00-00-00-00-00-00-00\"" | ||
463 | 420 | a = os.popen(a).read() | ||
464 | 421 | a = re.sub("\s+$", "", a) | ||
465 | 422 | a = re.sub(".+ ", "", a) | ||
466 | 423 | a = re.sub(":", "", a) | ||
467 | 424 | a = re.sub("-", "", a) | ||
468 | 425 | else: | ||
469 | 426 | for ifname in PossibleEthernetInterfaces: | ||
470 | 427 | try: | ||
471 | 428 | a = Linux_ioctl_GetInterfaceMac(ifname) | ||
472 | 429 | break | ||
473 | 430 | except IOError, e: | ||
474 | 431 | pass | ||
475 | 432 | return HexStringToByteArray(a) | ||
476 | 433 | |||
477 | 434 | def DeviceForIdePort(n): | ||
478 | 435 | if n > 3: | ||
479 | 436 | return None | ||
480 | 437 | g0 = "00000000" | ||
481 | 438 | if n > 1: | ||
482 | 439 | g0 = "00000001" | ||
483 | 440 | n = n - 2 | ||
484 | 441 | device = None | ||
485 | 442 | path = "/sys/bus/vmbus/devices/" | ||
486 | 443 | for vmbus in os.listdir(path): | ||
487 | 444 | guid = GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-') | ||
488 | 445 | if guid[0] == g0 and guid[1] == "000" + str(n): | ||
489 | 446 | for root, dirs, files in os.walk(path + vmbus): | ||
490 | 447 | if root.endswith("/block"): | ||
491 | 448 | device = dirs[0] | ||
492 | 449 | break | ||
493 | 450 | break | ||
494 | 451 | return device | ||
495 | 452 | |||
496 | 453 | class Util(object): | ||
497 | 454 | def _HttpGet(self, url, headers): | ||
498 | 455 | LogIfVerbose("HttpGet(" + url + ")") | ||
499 | 456 | maxRetry = 2 | ||
500 | 457 | if url.startswith("http://"): | ||
501 | 458 | url = url[7:] | ||
502 | 459 | url = url[url.index("/"):] | ||
503 | 460 | for retry in range(0, maxRetry + 1): | ||
504 | 461 | strRetry = str(retry) | ||
505 | 462 | log = [NoLog, Log][retry > 0] | ||
506 | 463 | log("retry HttpGet(" + url + "),retry=" + strRetry) | ||
507 | 464 | response = None | ||
508 | 465 | strStatus = "None" | ||
509 | 466 | try: | ||
510 | 467 | httpConnection = httplib.HTTPConnection(self.Endpoint) | ||
511 | 468 | if headers == None: | ||
512 | 469 | request = httpConnection.request("GET", url) | ||
513 | 470 | else: | ||
514 | 471 | request = httpConnection.request("GET", url, None, headers) | ||
515 | 472 | response = httpConnection.getresponse() | ||
516 | 473 | strStatus = str(response.status) | ||
517 | 474 | except: | ||
518 | 475 | pass | ||
519 | 476 | log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
520 | 477 | if response == None or response.status != httplib.OK: | ||
521 | 478 | Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
522 | 479 | if retry == maxRetry: | ||
523 | 480 | Log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
524 | 481 | return None | ||
525 | 482 | else: | ||
526 | 483 | Log("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
527 | 484 | time.sleep(10) | ||
528 | 485 | else: | ||
529 | 486 | log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
530 | 487 | return response.read() | ||
531 | 488 | |||
532 | 489 | def HttpGetWithoutHeaders(self, url): | ||
533 | 490 | return self._HttpGet(url, None) | ||
534 | 491 | |||
535 | 492 | def HttpGetWithHeaders(self, url): | ||
536 | 493 | return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion}) | ||
537 | 494 | |||
538 | 495 | def HttpSecureGetWithHeaders(self, url, transportCert): | ||
539 | 496 | return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, | ||
540 | 497 | "x-ms-version": ProtocolVersion, | ||
541 | 498 | "x-ms-cipher-name": "DES_EDE3_CBC", | ||
542 | 499 | "x-ms-guest-agent-public-x509-cert": transportCert}) | ||
543 | 500 | |||
544 | 501 | def HttpPost(self, url, data): | ||
545 | 502 | LogIfVerbose("HttpPost(" + url + ")") | ||
546 | 503 | maxRetry = 2 | ||
547 | 504 | for retry in range(0, maxRetry + 1): | ||
548 | 505 | strRetry = str(retry) | ||
549 | 506 | log = [NoLog, Log][retry > 0] | ||
550 | 507 | log("retry HttpPost(" + url + "),retry=" + strRetry) | ||
551 | 508 | response = None | ||
552 | 509 | strStatus = "None" | ||
553 | 510 | try: | ||
554 | 511 | httpConnection = httplib.HTTPConnection(self.Endpoint) | ||
555 | 512 | request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName, | ||
556 | 513 | "Content-Type": "text/xml; charset=utf-8", | ||
557 | 514 | "x-ms-version": ProtocolVersion}) | ||
558 | 515 | response = httpConnection.getresponse() | ||
559 | 516 | strStatus = str(response.status) | ||
560 | 517 | except: | ||
561 | 518 | pass | ||
562 | 519 | log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
563 | 520 | if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED): | ||
564 | 521 | Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
565 | 522 | if retry == maxRetry: | ||
566 | 523 | Log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
567 | 524 | return None | ||
568 | 525 | else: | ||
569 | 526 | Log("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
570 | 527 | time.sleep(10) | ||
571 | 528 | else: | ||
572 | 529 | log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
573 | 530 | return response | ||
574 | 531 | |||
575 | 532 | def LoadBalancerProbeServer(port): | ||
576 | 533 | |||
577 | 534 | class T(object): | ||
578 | 535 | def __init__(self, port): | ||
579 | 536 | self.ProbeCounter = 0 | ||
580 | 537 | self.server = SocketServer.TCPServer((GetIpv4Address(), port), TCPHandler) | ||
581 | 538 | self.server_thread = threading.Thread(target = self.server.serve_forever) | ||
582 | 539 | self.server_thread.setDaemon(True) | ||
583 | 540 | self.server_thread.start() | ||
584 | 541 | |||
585 | 542 | def shutdown(self): | ||
586 | 543 | self.server.shutdown() | ||
587 | 544 | |||
588 | 545 | class TCPHandler(SocketServer.BaseRequestHandler): | ||
589 | 546 | def GetHttpDateTimeNow(self): | ||
590 | 547 | # Date: Fri, 25 Mar 2011 04:53:10 GMT | ||
591 | 548 | return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) | ||
592 | 549 | |||
593 | 550 | def handle(self): | ||
594 | 551 | context.ProbeCounter = (context.ProbeCounter + 1) % 1000000 | ||
595 | 552 | log = [NoLog, LogIfVerbose][ThrottleLog(context.ProbeCounter)] | ||
596 | 553 | strCounter = str(context.ProbeCounter) | ||
597 | 554 | if context.ProbeCounter == 1: | ||
598 | 555 | Log("Receiving LB probes.") | ||
599 | 556 | log("Received LB probe # " + strCounter) | ||
600 | 557 | self.request.recv(1024) | ||
601 | 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") | ||
602 | 559 | |||
603 | 560 | context = T(port) | ||
604 | 561 | return context | ||
605 | 562 | |||
606 | 563 | class ConfigurationProvider(object): | ||
607 | 564 | def __init__(self): | ||
608 | 565 | self.values = dict() | ||
609 | 566 | if os.path.isfile("/etc/waagent.conf") == False: | ||
610 | 567 | raise Exception("Missing configuration in /etc/waagent.conf") | ||
611 | 568 | try: | ||
612 | 569 | for line in GetFileContents("/etc/waagent.conf").split('\n'): | ||
613 | 570 | if not line.startswith("#") and "=" in line: | ||
614 | 571 | parts = line.split()[0].split('=') | ||
615 | 572 | value = parts[1].strip("\" ") | ||
616 | 573 | if value != "None": | ||
617 | 574 | self.values[parts[0]] = value | ||
618 | 575 | else: | ||
619 | 576 | self.values[parts[0]] = None | ||
620 | 577 | except: | ||
621 | 578 | Error("Unable to parse /etc/waagent.conf") | ||
622 | 579 | raise | ||
623 | 580 | return | ||
624 | 581 | |||
625 | 582 | def get(self, key): | ||
626 | 583 | return self.values.get(key) | ||
627 | 584 | |||
628 | 585 | class EnvMonitor(object): | ||
629 | 586 | def __init__(self): | ||
630 | 587 | self.shutdown = False | ||
631 | 588 | self.HostName = socket.gethostname() | ||
632 | 589 | self.server_thread = threading.Thread(target = self.monitor) | ||
633 | 590 | self.server_thread.setDaemon(True) | ||
634 | 591 | self.server_thread.start() | ||
635 | 592 | self.published = False | ||
636 | 593 | |||
637 | 594 | def monitor(self): | ||
638 | 595 | publish = Config.get("Provisioning.MonitorHostName") | ||
639 | 596 | dhcpcmd = "pidof dhclient" | ||
640 | 597 | if IsSuse(): | ||
641 | 598 | dhcpcmd = "pidof dhcpcd" | ||
642 | 599 | if IsDebian(): | ||
643 | 600 | dhcpcmd = "pidof dhclient3" | ||
644 | 601 | dhcppid = os.popen(dhcpcmd).read() | ||
645 | 602 | while not self.shutdown: | ||
646 | 603 | for a in RulesFiles: | ||
647 | 604 | if os.path.isfile(a): | ||
648 | 605 | if os.path.isfile(GetLastPathElement(a)): | ||
649 | 606 | os.remove(GetLastPathElement(a)) | ||
650 | 607 | shutil.move(a, ".") | ||
651 | 608 | Log("EnvMonitor: Moved " + a + " -> " + LibDir) | ||
652 | 609 | if publish != None and publish.lower().startswith("y"): | ||
653 | 610 | try: | ||
654 | 611 | if socket.gethostname() != self.HostName: | ||
655 | 612 | Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname()) | ||
656 | 613 | self.HostName = socket.gethostname() | ||
657 | 614 | WaAgent.UpdateAndPublishHostName(self.HostName) | ||
658 | 615 | dhcppid = os.popen(dhcpcmd).read() | ||
659 | 616 | self.published = True | ||
660 | 617 | except: | ||
661 | 618 | pass | ||
662 | 619 | else: | ||
663 | 620 | self.published = True | ||
664 | 621 | pid = "" | ||
665 | 622 | if not os.path.isdir("/proc/" + dhcppid.strip()): | ||
666 | 623 | pid = os.popen(dhcpcmd).read() | ||
667 | 624 | if pid != "" and pid != dhcppid: | ||
668 | 625 | Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.") | ||
669 | 626 | WaAgent.RestoreRoutes() | ||
670 | 627 | dhcppid = pid | ||
671 | 628 | for child in Children: | ||
672 | 629 | if child.poll() != None: | ||
673 | 630 | Children.remove(child) | ||
674 | 631 | time.sleep(5) | ||
675 | 632 | |||
676 | 633 | def SetHostName(self, name): | ||
677 | 634 | if socket.gethostname() == name: | ||
678 | 635 | self.published = True | ||
679 | 636 | else: | ||
680 | 637 | Run("hostname " + name) | ||
681 | 638 | |||
682 | 639 | def IsNamePublished(self): | ||
683 | 640 | return self.published | ||
684 | 641 | |||
685 | 642 | def ShutdownService(self): | ||
686 | 643 | self.shutdown = True | ||
687 | 644 | self.server_thread.join() | ||
688 | 645 | |||
689 | 646 | class Certificates(object): | ||
690 | 647 | # | ||
691 | 648 | # <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd"> | ||
692 | 649 | # <Version>2010-12-15</Version> | ||
693 | 650 | # <Incarnation>2</Incarnation> | ||
694 | 651 | # <Format>Pkcs7BlobWithPfxContents</Format> | ||
695 | 652 | # <Data>MIILTAY... | ||
696 | 653 | # </Data> | ||
697 | 654 | # </CertificateFile> | ||
698 | 655 | # | ||
699 | 656 | def __init__(self): | ||
700 | 657 | self.reinitialize() | ||
701 | 658 | |||
702 | 659 | def reinitialize(self): | ||
703 | 660 | self.Incarnation = None | ||
704 | 661 | self.Role = None | ||
705 | 662 | |||
706 | 663 | def Parse(self, xmlText): | ||
707 | 664 | self.reinitialize() | ||
708 | 665 | SetFileContents("Certificates.xml", xmlText) | ||
709 | 666 | dom = xml.dom.minidom.parseString(xmlText) | ||
710 | 667 | for a in [ "CertificateFile", "Version", "Incarnation", | ||
711 | 668 | "Format", "Data", ]: | ||
712 | 669 | if not dom.getElementsByTagName(a): | ||
713 | 670 | Error("Certificates.Parse: Missing " + a) | ||
714 | 671 | return None | ||
715 | 672 | node = dom.childNodes[0] | ||
716 | 673 | if node.localName != "CertificateFile": | ||
717 | 674 | Error("Certificates.Parse: root not CertificateFile") | ||
718 | 675 | return None | ||
719 | 676 | SetFileContents("Certificates.p7m", | ||
720 | 677 | "MIME-Version: 1.0\n" | ||
721 | 678 | + "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n" | ||
722 | 679 | + "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n" | ||
723 | 680 | + "Content-Transfer-Encoding: base64\n\n" | ||
724 | 681 | + GetNodeTextData(dom.getElementsByTagName("Data")[0])) | ||
725 | 682 | if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"): | ||
726 | 683 | Error("Certificates.Parse: Failed to extract certificates from CMS message.") | ||
727 | 684 | return self | ||
728 | 685 | # There may be multiple certificates in this package. Split them. | ||
729 | 686 | file = open("Certificates.pem") | ||
730 | 687 | pindex = 1 | ||
731 | 688 | cindex = 1 | ||
732 | 689 | output = open("temp.pem", "w") | ||
733 | 690 | for line in file.readlines(): | ||
734 | 691 | output.write(line) | ||
735 | 692 | if line.startswith("-----END PRIVATE KEY-----") or line.startswith("-----END CERTIFICATE-----"): | ||
736 | 693 | output.close() | ||
737 | 694 | if line.startswith("-----END PRIVATE KEY-----"): | ||
738 | 695 | os.rename("temp.pem", str(pindex) + ".prv") | ||
739 | 696 | pindex += 1 | ||
740 | 697 | else: | ||
741 | 698 | os.rename("temp.pem", str(cindex) + ".crt") | ||
742 | 699 | cindex += 1 | ||
743 | 700 | output = open("temp.pem", "w") | ||
744 | 701 | output.close() | ||
745 | 702 | os.remove("temp.pem") | ||
746 | 703 | keys = dict() | ||
747 | 704 | index = 1 | ||
748 | 705 | filename = str(index) + ".crt" | ||
749 | 706 | while os.path.isfile(filename): | ||
750 | 707 | thumbprint = os.popen(Openssl + " x509 -in " + filename + " -fingerprint -noout").read().rstrip().split('=')[1].replace(':', '').upper() | ||
751 | 708 | pubkey=os.popen(Openssl + " x509 -in " + filename + " -pubkey -noout").read() | ||
752 | 709 | keys[pubkey] = thumbprint | ||
753 | 710 | os.rename(filename, thumbprint + ".crt") | ||
754 | 711 | os.chmod(thumbprint + ".crt", 0600) | ||
755 | 712 | if IsRedHat(): | ||
756 | 713 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + thumbprint + ".crt") | ||
757 | 714 | index += 1 | ||
758 | 715 | filename = str(index) + ".crt" | ||
759 | 716 | index = 1 | ||
760 | 717 | filename = str(index) + ".prv" | ||
761 | 718 | while os.path.isfile(filename): | ||
762 | 719 | pubkey = os.popen(Openssl + " rsa -in " + filename + " -pubout").read() | ||
763 | 720 | os.rename(filename, keys[pubkey] + ".prv") | ||
764 | 721 | os.chmod(keys[pubkey] + ".prv", 0600) | ||
765 | 722 | if IsRedHat(): | ||
766 | 723 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + keys[pubkey] + ".prv") | ||
767 | 724 | index += 1 | ||
768 | 725 | filename = str(index) + ".prv" | ||
769 | 726 | return self | ||
770 | 727 | |||
771 | 728 | class SharedConfig(object): | ||
772 | 729 | # | ||
773 | 730 | # <SharedConfig version="1.0.0.0" goalStateIncarnation="1"> | ||
774 | 731 | # <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2"> | ||
775 | 732 | # <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" /> | ||
776 | 733 | # <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" /> | ||
777 | 734 | # </Deployment> | ||
778 | 735 | # <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" /> | ||
779 | 736 | # <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" /> | ||
780 | 737 | # <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8"> | ||
781 | 738 | # <Probes> | ||
782 | 739 | # <Probe name="MachineRole" /> | ||
783 | 740 | # <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" /> | ||
784 | 741 | # <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" /> | ||
785 | 742 | # </Probes> | ||
786 | 743 | # </LoadBalancerSettings> | ||
787 | 744 | # <OutputEndpoints> | ||
788 | 745 | # <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS"> | ||
789 | 746 | # <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" /> | ||
790 | 747 | # </Endpoint> | ||
791 | 748 | # </OutputEndpoints> | ||
792 | 749 | # <Instances> | ||
793 | 750 | # <Instance id="MachineRole_IN_0" address="10.115.153.75"> | ||
794 | 751 | # <FaultDomains randomId="0" updateId="0" updateCount="0" /> | ||
795 | 752 | # <InputEndpoints> | ||
796 | 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"> | ||
797 | 754 | # <LocalPorts> | ||
798 | 755 | # <LocalPortRange from="80" to="80" /> | ||
799 | 756 | # </LocalPorts> | ||
800 | 757 | # </Endpoint> | ||
801 | 758 | # <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false"> | ||
802 | 759 | # <LocalPorts> | ||
803 | 760 | # <LocalPortRange from="3389" to="3389" /> | ||
804 | 761 | # </LocalPorts> | ||
805 | 762 | # </Endpoint> | ||
806 | 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"> | ||
807 | 764 | # <LocalPorts> | ||
808 | 765 | # <LocalPortRange from="20000" to="20000" /> | ||
809 | 766 | # </LocalPorts> | ||
810 | 767 | # </Endpoint> | ||
811 | 768 | # </InputEndpoints> | ||
812 | 769 | # </Instance> | ||
813 | 770 | # </Instances> | ||
814 | 771 | # </SharedConfig> | ||
815 | 772 | # | ||
816 | 773 | def __init__(self): | ||
817 | 774 | self.reinitialize() | ||
818 | 775 | |||
819 | 776 | def reinitialize(self): | ||
820 | 777 | self.Deployment = None | ||
821 | 778 | self.Incarnation = None | ||
822 | 779 | self.Role = None | ||
823 | 780 | self.LoadBalancerSettings = None | ||
824 | 781 | self.OutputEndpoints = None | ||
825 | 782 | self.Instances = None | ||
826 | 783 | |||
827 | 784 | def Parse(self, xmlText): | ||
828 | 785 | self.reinitialize() | ||
829 | 786 | SetFileContents("SharedConfig.xml", xmlText) | ||
830 | 787 | dom = xml.dom.minidom.parseString(xmlText) | ||
831 | 788 | for a in [ "SharedConfig", "Deployment", "Service", | ||
832 | 789 | "ServiceInstance", "Incarnation", "Role", ]: | ||
833 | 790 | if not dom.getElementsByTagName(a): | ||
834 | 791 | Error("SharedConfig.Parse: Missing " + a) | ||
835 | 792 | return None | ||
836 | 793 | node = dom.childNodes[0] | ||
837 | 794 | if node.localName != "SharedConfig": | ||
838 | 795 | Error("SharedConfig.Parse: root not SharedConfig") | ||
839 | 796 | return None | ||
840 | 797 | program = Config.get("Role.TopologyConsumer") | ||
841 | 798 | if program != None: | ||
842 | 799 | Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"])) | ||
843 | 800 | return self | ||
844 | 801 | |||
845 | 802 | class HostingEnvironmentConfig(object): | ||
846 | 803 | # | ||
847 | 804 | # <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1"> | ||
848 | 805 | # <StoredCertificates> | ||
849 | 806 | # <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" /> | ||
850 | 807 | # </StoredCertificates> | ||
851 | 808 | # <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2"> | ||
852 | 809 | # <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" /> | ||
853 | 810 | # <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" /> | ||
854 | 811 | # </Deployment> | ||
855 | 812 | # <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" /> | ||
856 | 813 | # <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" /> | ||
857 | 814 | # <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip"> | ||
858 | 815 | # <CAS mode="full" /> | ||
859 | 816 | # <PrivilegeLevel mode="max" /> | ||
860 | 817 | # <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties> | ||
861 | 818 | # </HostingEnvironmentSettings> | ||
862 | 819 | # <ApplicationSettings> | ||
863 | 820 | # <Setting name="__ModelData" value="<m role="MachineRole" xmlns="urn:azure:m:v1"><r name="MachineRole"><e name="a" /><e name="b" /><e name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" /><e name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" /></r></m>" /> | ||
864 | 821 | # <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." /> | ||
865 | 822 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcN..." /> | ||
866 | 823 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2022-07-23T23:59:59.0000000-07:00" /> | ||
867 | 824 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test" /> | ||
868 | 825 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" /> | ||
869 | 826 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" /> | ||
870 | 827 | # <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" /> | ||
871 | 828 | # </ApplicationSettings> | ||
872 | 829 | # <ResourceReferences> | ||
873 | 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" /> | ||
874 | 831 | # </ResourceReferences> | ||
875 | 832 | # </HostingEnvironmentConfig> | ||
876 | 833 | # | ||
877 | 834 | def __init__(self): | ||
878 | 835 | self.reinitialize() | ||
879 | 836 | |||
880 | 837 | def reinitialize(self): | ||
881 | 838 | self.StoredCertificates = None | ||
882 | 839 | self.Deployment = None | ||
883 | 840 | self.Incarnation = None | ||
884 | 841 | self.Role = None | ||
885 | 842 | self.HostingEnvironmentSettings = None | ||
886 | 843 | self.ApplicationSettings = None | ||
887 | 844 | self.Certificates = None | ||
888 | 845 | self.ResourceReferences = None | ||
889 | 846 | |||
890 | 847 | def Parse(self, xmlText): | ||
891 | 848 | self.reinitialize() | ||
892 | 849 | SetFileContents("HostingEnvironmentConfig.xml", xmlText) | ||
893 | 850 | dom = xml.dom.minidom.parseString(xmlText) | ||
894 | 851 | for a in [ "HostingEnvironmentConfig", "Deployment", "Service", | ||
895 | 852 | "ServiceInstance", "Incarnation", "Role", ]: | ||
896 | 853 | if not dom.getElementsByTagName(a): | ||
897 | 854 | Error("HostingEnvironmentConfig.Parse: Missing " + a) | ||
898 | 855 | return None | ||
899 | 856 | node = dom.childNodes[0] | ||
900 | 857 | if node.localName != "HostingEnvironmentConfig": | ||
901 | 858 | Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig") | ||
902 | 859 | return None | ||
903 | 860 | self.ApplicationSettings = dom.getElementsByTagName("Setting") | ||
904 | 861 | self.Certificates = dom.getElementsByTagName("StoredCertificate") | ||
905 | 862 | return self | ||
906 | 863 | |||
907 | 864 | def DecryptPassword(self, e): | ||
908 | 865 | SetFileContents("password.p7m", | ||
909 | 866 | "MIME-Version: 1.0\n" | ||
910 | 867 | + "Content-Disposition: attachment; filename=\"password.p7m\"\n" | ||
911 | 868 | + "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n" | ||
912 | 869 | + "Content-Transfer-Encoding: base64\n\n" | ||
913 | 870 | + textwrap.fill(e, 64)) | ||
914 | 871 | return os.popen(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem").read() | ||
915 | 872 | |||
916 | 873 | def ActivateResourceDisk(self): | ||
917 | 874 | global DiskActivated | ||
918 | 875 | if IsWindows(): | ||
919 | 876 | DiskActivated = True | ||
920 | 877 | Log("Skipping ActivateResourceDisk on Windows") | ||
921 | 878 | return | ||
922 | 879 | format = Config.get("ResourceDisk.Format") | ||
923 | 880 | if format == None or format.lower().startswith("n"): | ||
924 | 881 | DiskActivated = True | ||
925 | 882 | return | ||
926 | 883 | device = DeviceForIdePort(1) | ||
927 | 884 | if device == None: | ||
928 | 885 | Error("ActivateResourceDisk: Unable to detect disk topology.") | ||
929 | 886 | return | ||
930 | 887 | device = "/dev/" + device | ||
931 | 888 | for entry in os.popen("mount").read().split(): | ||
932 | 889 | if entry.startswith(device + "1"): | ||
933 | 890 | Log("ActivateResourceDisk: " + device + "1 is already mounted.") | ||
934 | 891 | DiskActivated = True | ||
935 | 892 | return | ||
936 | 893 | mountpoint = Config.get("ResourceDisk.MountPoint") | ||
937 | 894 | if mountpoint == None: | ||
938 | 895 | mountpoint = "/mnt/resource" | ||
939 | 896 | CreateDir(mountpoint, "root", 0755) | ||
940 | 897 | fs = Config.get("ResourceDisk.Filesystem") | ||
941 | 898 | if fs == None: | ||
942 | 899 | fs = "ext3" | ||
943 | 900 | if os.popen("sfdisk -q -c " + device + " 1").read().rstrip() == "7" and fs != "ntfs": | ||
944 | 901 | Run("sfdisk -c " + device + " 1 83") | ||
945 | 902 | Run("mkfs." + fs + " " + device + "1") | ||
946 | 903 | if Run("mount " + device + "1 " + mountpoint): | ||
947 | 904 | Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).") | ||
948 | 905 | return | ||
949 | 906 | Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs) | ||
950 | 907 | DiskActivated = True | ||
951 | 908 | swap = Config.get("ResourceDisk.EnableSwap") | ||
952 | 909 | if swap == None or swap.lower().startswith("n"): | ||
953 | 910 | return | ||
954 | 911 | sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024 | ||
955 | 912 | if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024): | ||
956 | 913 | os.remove(mountpoint + "/swapfile") | ||
957 | 914 | if not os.path.isfile(mountpoint + "/swapfile"): | ||
958 | 915 | Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB)) | ||
959 | 916 | Run("mkswap " + mountpoint + "/swapfile") | ||
960 | 917 | if not Run("swapon " + mountpoint + "/swapfile"): | ||
961 | 918 | Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile") | ||
962 | 919 | else: | ||
963 | 920 | Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile") | ||
964 | 921 | |||
965 | 922 | def Process(self): | ||
966 | 923 | if DiskActivated == False: | ||
967 | 924 | diskThread = threading.Thread(target = self.ActivateResourceDisk) | ||
968 | 925 | diskThread.start() | ||
969 | 926 | User = None | ||
970 | 927 | Pass = None | ||
971 | 928 | Expiration = None | ||
972 | 929 | Thumbprint = None | ||
973 | 930 | for b in self.ApplicationSettings: | ||
974 | 931 | sname = b.getAttribute("name") | ||
975 | 932 | svalue = b.getAttribute("value") | ||
976 | 933 | if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword": | ||
977 | 934 | Pass = self.DecryptPassword(svalue) | ||
978 | 935 | elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername": | ||
979 | 936 | User = svalue | ||
980 | 937 | elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration": | ||
981 | 938 | Expiration = svalue | ||
982 | 939 | elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption": | ||
983 | 940 | Thumbprint = svalue.split(':')[1].upper() | ||
984 | 941 | if User != None and Pass != None: | ||
985 | 942 | if User != "root" and User != "" and Pass != "": | ||
986 | 943 | CreateAccount(User, Pass, Expiration, Thumbprint) | ||
987 | 944 | else: | ||
988 | 945 | Error("Not creating user account: " + User) | ||
989 | 946 | for c in self.Certificates: | ||
990 | 947 | cname = c.getAttribute("name") | ||
991 | 948 | csha1 = c.getAttribute("certificateId").split(':')[1].upper() | ||
992 | 949 | cpath = c.getAttribute("storeName") | ||
993 | 950 | clevel = c.getAttribute("configurationLevel") | ||
994 | 951 | if os.path.isfile(csha1 + ".prv"): | ||
995 | 952 | Log("Private key with thumbprint: " + csha1 + " was retrieved.") | ||
996 | 953 | if os.path.isfile(csha1 + ".crt"): | ||
997 | 954 | Log("Public cert with thumbprint: " + csha1 + " was retrieved.") | ||
998 | 955 | program = Config.get("Role.ConfigurationConsumer") | ||
999 | 956 | if program != None: | ||
1000 | 957 | Children.append(subprocess.Popen([program, LibDir + "/HostingEnvironmentConfig.xml"])) | ||
1001 | 958 | |||
1002 | 959 | class GoalState(Util): | ||
1003 | 960 | # | ||
1004 | 961 | # <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd"> | ||
1005 | 962 | # <Version>2010-12-15</Version> | ||
1006 | 963 | # <Incarnation>1</Incarnation> | ||
1007 | 964 | # <Machine> | ||
1008 | 965 | # <ExpectedState>Started</ExpectedState> | ||
1009 | 966 | # <LBProbePorts> | ||
1010 | 967 | # <Port>16001</Port> | ||
1011 | 968 | # </LBProbePorts> | ||
1012 | 969 | # </Machine> | ||
1013 | 970 | # <Container> | ||
1014 | 971 | # <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId> | ||
1015 | 972 | # <RoleInstanceList> | ||
1016 | 973 | # <RoleInstance> | ||
1017 | 974 | # <InstanceId>MachineRole_IN_0</InstanceId> | ||
1018 | 975 | # <State>Started</State> | ||
1019 | 976 | # <Configuration> | ||
1020 | 977 | # <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=hostingEnvironmentConfig&incarnation=1</HostingEnvironmentConfig> | ||
1021 | 978 | # <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=sharedConfig&incarnation=1</SharedConfig> | ||
1022 | 979 | # <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&incarnation=1</Certificates> | ||
1023 | 980 | # </Configuration> | ||
1024 | 981 | # </RoleInstance> | ||
1025 | 982 | # </RoleInstanceList> | ||
1026 | 983 | # </Container> | ||
1027 | 984 | # </GoalState> | ||
1028 | 985 | # | ||
1029 | 986 | # There is only one Role for VM images. | ||
1030 | 987 | # | ||
1031 | 988 | # Of primary interest is: | ||
1032 | 989 | # Machine/ExpectedState -- this is how shutdown is requested | ||
1033 | 990 | # LBProbePorts -- an http server needs to run here | ||
1034 | 991 | # We also note Container/ContainerID and RoleInstance/InstanceId to form the health report. | ||
1035 | 992 | # And of course, Incarnation | ||
1036 | 993 | # | ||
1037 | 994 | def __init__(self, Agent): | ||
1038 | 995 | self.Agent = Agent | ||
1039 | 996 | self.Endpoint = Agent.Endpoint | ||
1040 | 997 | self.TransportCert = Agent.TransportCert | ||
1041 | 998 | self.reinitialize() | ||
1042 | 999 | |||
1043 | 1000 | def reinitialize(self): | ||
1044 | 1001 | self.Incarnation = None # integer | ||
1045 | 1002 | self.ExpectedState = None # "Started" or "Stopped" | ||
1046 | 1003 | self.HostingEnvironmentConfigUrl = None | ||
1047 | 1004 | self.HostingEnvironmentConfigXml = None | ||
1048 | 1005 | self.HostingEnvironmentConfig = None | ||
1049 | 1006 | self.SharedConfigUrl = None | ||
1050 | 1007 | self.SharedConfigXml = None | ||
1051 | 1008 | self.SharedConfig = None | ||
1052 | 1009 | self.CertificatesUrl = None | ||
1053 | 1010 | self.CertificatesXml = None | ||
1054 | 1011 | self.Certificates = None | ||
1055 | 1012 | self.RoleInstanceId = None | ||
1056 | 1013 | self.ContainerId = None | ||
1057 | 1014 | self.LoadBalancerProbePort = None # integer, ?list of integers | ||
1058 | 1015 | |||
1059 | 1016 | def Parse(self, xmlText): | ||
1060 | 1017 | self.reinitialize() | ||
1061 | 1018 | node = xml.dom.minidom.parseString(xmlText).childNodes[0] | ||
1062 | 1019 | if node.localName != "GoalState": | ||
1063 | 1020 | Error("GoalState.Parse: root not GoalState") | ||
1064 | 1021 | return None | ||
1065 | 1022 | for a in node.childNodes: | ||
1066 | 1023 | if a.nodeType == node.ELEMENT_NODE: | ||
1067 | 1024 | if a.localName == "Incarnation": | ||
1068 | 1025 | self.Incarnation = GetNodeTextData(a) | ||
1069 | 1026 | elif a.localName == "Machine": | ||
1070 | 1027 | for b in a.childNodes: | ||
1071 | 1028 | if b.nodeType == node.ELEMENT_NODE: | ||
1072 | 1029 | if b.localName == "ExpectedState": | ||
1073 | 1030 | self.ExpectedState = GetNodeTextData(b) | ||
1074 | 1031 | Log("ExpectedState: " + self.ExpectedState) | ||
1075 | 1032 | elif b.localName == "LBProbePorts": | ||
1076 | 1033 | for c in b.childNodes: | ||
1077 | 1034 | if c.nodeType == node.ELEMENT_NODE and c.localName == "Port": | ||
1078 | 1035 | self.LoadBalancerProbePort = int(GetNodeTextData(c)) | ||
1079 | 1036 | elif a.localName == "Container": | ||
1080 | 1037 | for b in a.childNodes: | ||
1081 | 1038 | if b.nodeType == node.ELEMENT_NODE: | ||
1082 | 1039 | if b.localName == "ContainerId": | ||
1083 | 1040 | self.ContainerId = GetNodeTextData(b) | ||
1084 | 1041 | Log("ContainerId: " + self.ContainerId) | ||
1085 | 1042 | elif b.localName == "RoleInstanceList": | ||
1086 | 1043 | for c in b.childNodes: | ||
1087 | 1044 | if c.localName == "RoleInstance": | ||
1088 | 1045 | for d in c.childNodes: | ||
1089 | 1046 | if d.nodeType == node.ELEMENT_NODE: | ||
1090 | 1047 | if d.localName == "InstanceId": | ||
1091 | 1048 | self.RoleInstanceId = GetNodeTextData(d) | ||
1092 | 1049 | Log("RoleInstanceId: " + self.RoleInstanceId) | ||
1093 | 1050 | elif d.localName == "State": | ||
1094 | 1051 | pass | ||
1095 | 1052 | elif d.localName == "Configuration": | ||
1096 | 1053 | for e in d.childNodes: | ||
1097 | 1054 | if e.nodeType == node.ELEMENT_NODE: | ||
1098 | 1055 | if e.localName == "HostingEnvironmentConfig": | ||
1099 | 1056 | self.HostingEnvironmentConfigUrl = GetNodeTextData(e) | ||
1100 | 1057 | LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl) | ||
1101 | 1058 | self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl) | ||
1102 | 1059 | self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml) | ||
1103 | 1060 | elif e.localName == "SharedConfig": | ||
1104 | 1061 | self.SharedConfigUrl = GetNodeTextData(e) | ||
1105 | 1062 | LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl) | ||
1106 | 1063 | self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl) | ||
1107 | 1064 | self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml) | ||
1108 | 1065 | elif e.localName == "Certificates": | ||
1109 | 1066 | self.CertificatesUrl = GetNodeTextData(e) | ||
1110 | 1067 | LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl) | ||
1111 | 1068 | self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert) | ||
1112 | 1069 | self.Certificates = Certificates().Parse(self.CertificatesXml) | ||
1113 | 1070 | if self.Incarnation == None: | ||
1114 | 1071 | Error("GoalState.Parse: Incarnation missing") | ||
1115 | 1072 | return None | ||
1116 | 1073 | if self.ExpectedState == None: | ||
1117 | 1074 | Error("GoalState.Parse: ExpectedState missing") | ||
1118 | 1075 | return None | ||
1119 | 1076 | if self.RoleInstanceId == None: | ||
1120 | 1077 | Error("GoalState.Parse: RoleInstanceId missing") | ||
1121 | 1078 | return None | ||
1122 | 1079 | if self.ContainerId == None: | ||
1123 | 1080 | Error("GoalState.Parse: ContainerId missing") | ||
1124 | 1081 | return None | ||
1125 | 1082 | SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText) | ||
1126 | 1083 | return self | ||
1127 | 1084 | |||
1128 | 1085 | def Process(self): | ||
1129 | 1086 | self.HostingEnvironmentConfig.Process() | ||
1130 | 1087 | |||
1131 | 1088 | class OvfEnv(object): | ||
1132 | 1089 | # | ||
1133 | 1090 | # <?xml version="1.0" encoding="utf-8"?> | ||
1134 | 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"> | ||
1135 | 1092 | # <wa:ProvisioningSection> | ||
1136 | 1093 | # <wa:Version>1.0</wa:Version> | ||
1137 | 1094 | # <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> | ||
1138 | 1095 | # <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType> | ||
1139 | 1096 | # <HostName>HostName</HostName> | ||
1140 | 1097 | # <UserName>UserName</UserName> | ||
1141 | 1098 | # <UserPassword>UserPassword</UserPassword> | ||
1142 | 1099 | # <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication> | ||
1143 | 1100 | # <SSH> | ||
1144 | 1101 | # <PublicKeys> | ||
1145 | 1102 | # <PublicKey> | ||
1146 | 1103 | # <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> | ||
1147 | 1104 | # <Path>$HOME/UserName/.ssh/authorized_keys</Path> | ||
1148 | 1105 | # </PublicKey> | ||
1149 | 1106 | # </PublicKeys> | ||
1150 | 1107 | # <KeyPairs> | ||
1151 | 1108 | # <KeyPair> | ||
1152 | 1109 | # <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> | ||
1153 | 1110 | # <Path>$HOME/UserName/.ssh/id_rsa</Path> | ||
1154 | 1111 | # </KeyPair> | ||
1155 | 1112 | # </KeyPairs> | ||
1156 | 1113 | # </SSH> | ||
1157 | 1114 | # </LinuxProvisioningConfigurationSet> | ||
1158 | 1115 | # </wa:ProvisioningSection> | ||
1159 | 1116 | # </Environment> | ||
1160 | 1117 | # | ||
1161 | 1118 | def __init__(self): | ||
1162 | 1119 | self.reinitialize() | ||
1163 | 1120 | |||
1164 | 1121 | def reinitialize(self): | ||
1165 | 1122 | self.WaNs = "http://schemas.microsoft.com/windowsazure" | ||
1166 | 1123 | self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1" | ||
1167 | 1124 | self.MajorVersion = 1 | ||
1168 | 1125 | self.MinorVersion = 0 | ||
1169 | 1126 | self.ComputerName = None | ||
1170 | 1127 | self.AdminPassword = None | ||
1171 | 1128 | self.UserName = None | ||
1172 | 1129 | self.UserPassword = None | ||
1173 | 1130 | self.DisableSshPasswordAuthentication = True | ||
1174 | 1131 | self.SshPublicKeys = [] | ||
1175 | 1132 | self.SshKeyPairs = [] | ||
1176 | 1133 | |||
1177 | 1134 | def Parse(self, xmlText): | ||
1178 | 1135 | self.reinitialize() | ||
1179 | 1136 | dom = xml.dom.minidom.parseString(xmlText) | ||
1180 | 1137 | if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1: | ||
1181 | 1138 | Error("Unable to parse OVF XML.") | ||
1182 | 1139 | section = None | ||
1183 | 1140 | newer = False | ||
1184 | 1141 | for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"): | ||
1185 | 1142 | for n in p.childNodes: | ||
1186 | 1143 | if n.localName == "Version": | ||
1187 | 1144 | verparts = GetNodeTextData(n).split('.') | ||
1188 | 1145 | major = int(verparts[0]) | ||
1189 | 1146 | minor = int(verparts[1]) | ||
1190 | 1147 | if major > self.MajorVersion: | ||
1191 | 1148 | newer = True | ||
1192 | 1149 | if major != self.MajorVersion: | ||
1193 | 1150 | break | ||
1194 | 1151 | if minor > self.MinorVersion: | ||
1195 | 1152 | newer = True | ||
1196 | 1153 | section = p | ||
1197 | 1154 | if newer == True: | ||
1198 | 1155 | Warn("Newer provisioning configuration detected. Please consider updating waagent.") | ||
1199 | 1156 | if section == None: | ||
1200 | 1157 | Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion)) | ||
1201 | 1158 | return None | ||
1202 | 1159 | self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0]) | ||
1203 | 1160 | self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0]) | ||
1204 | 1161 | try: | ||
1205 | 1162 | self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0]) | ||
1206 | 1163 | except: | ||
1207 | 1164 | pass | ||
1208 | 1165 | disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication") | ||
1209 | 1166 | if len(disableSshPass) != 0: | ||
1210 | 1167 | self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true") | ||
1211 | 1168 | for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"): | ||
1212 | 1169 | fp = None | ||
1213 | 1170 | path = None | ||
1214 | 1171 | for c in pkey.childNodes: | ||
1215 | 1172 | if c.localName == "Fingerprint": | ||
1216 | 1173 | fp = GetNodeTextData(c).upper() | ||
1217 | 1174 | if c.localName == "Path": | ||
1218 | 1175 | path = GetNodeTextData(c) | ||
1219 | 1176 | self.SshPublicKeys += [[fp, path]] | ||
1220 | 1177 | for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"): | ||
1221 | 1178 | fp = None | ||
1222 | 1179 | path = None | ||
1223 | 1180 | for c in keyp.childNodes: | ||
1224 | 1181 | if c.localName == "Fingerprint": | ||
1225 | 1182 | fp = GetNodeTextData(c).upper() | ||
1226 | 1183 | if c.localName == "Path": | ||
1227 | 1184 | path = GetNodeTextData(c) | ||
1228 | 1185 | self.SshKeyPairs += [[fp, path]] | ||
1229 | 1186 | return self | ||
1230 | 1187 | |||
1231 | 1188 | def PrepareDir(self, filepath): | ||
1232 | 1189 | home = GetHome() | ||
1233 | 1190 | # Expand HOME variable if present in path | ||
1234 | 1191 | path = os.path.normpath(filepath.replace("$HOME", home)) | ||
1235 | 1192 | if (path.startswith("/") == False) or (path.endswith("/") == True): | ||
1236 | 1193 | return None | ||
1237 | 1194 | dir = path.rsplit('/', 1)[0] | ||
1238 | 1195 | if dir != "": | ||
1239 | 1196 | CreateDir(dir, "root", 0700) | ||
1240 | 1197 | if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")): | ||
1241 | 1198 | ChangeOwner(dir, self.UserName) | ||
1242 | 1199 | return path | ||
1243 | 1200 | |||
1244 | 1201 | def NumberToBytes(self, i): | ||
1245 | 1202 | result = [] | ||
1246 | 1203 | while i: | ||
1247 | 1204 | result.append(chr(i & 0xFF)) | ||
1248 | 1205 | i >>= 8 | ||
1249 | 1206 | result.reverse() | ||
1250 | 1207 | return ''.join(result) | ||
1251 | 1208 | |||
1252 | 1209 | def BitsToString(self, a): | ||
1253 | 1210 | index=7 | ||
1254 | 1211 | s = "" | ||
1255 | 1212 | c = 0 | ||
1256 | 1213 | for bit in a: | ||
1257 | 1214 | c = c | (bit << index) | ||
1258 | 1215 | index = index - 1 | ||
1259 | 1216 | if index == -1: | ||
1260 | 1217 | s = s + struct.pack('>B', c) | ||
1261 | 1218 | c = 0 | ||
1262 | 1219 | index = 7 | ||
1263 | 1220 | return s | ||
1264 | 1221 | |||
1265 | 1222 | def OpensslToSsh(self, file): | ||
1266 | 1223 | from pyasn1.codec.der import decoder as der_decoder | ||
1267 | 1224 | try: | ||
1268 | 1225 | f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0] | ||
1269 | 1226 | k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0] | ||
1270 | 1227 | n=k[0] | ||
1271 | 1228 | e=k[1] | ||
1272 | 1229 | keydata="" | ||
1273 | 1230 | keydata += struct.pack('>I',len("ssh-rsa")) | ||
1274 | 1231 | keydata += "ssh-rsa" | ||
1275 | 1232 | keydata += struct.pack('>I',len(self.NumberToBytes(e))) | ||
1276 | 1233 | keydata += self.NumberToBytes(e) | ||
1277 | 1234 | keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1) | ||
1278 | 1235 | keydata += "\0" | ||
1279 | 1236 | keydata += self.NumberToBytes(n) | ||
1280 | 1237 | except Exception, e: | ||
1281 | 1238 | print("OpensslToSsh: Exception " + str(e)) | ||
1282 | 1239 | return None | ||
1283 | 1240 | return "ssh-rsa " + base64.b64encode(keydata) + "\n" | ||
1284 | 1241 | |||
1285 | 1242 | def Process(self): | ||
1286 | 1243 | error = None | ||
1287 | 1244 | WaAgent.EnvMonitor.SetHostName(self.ComputerName) | ||
1288 | 1245 | if self.DisableSshPasswordAuthentication: | ||
1289 | 1246 | filepath = "/etc/ssh/sshd_config" | ||
1290 | 1247 | # Disable RFC 4252 and RFC 4256 authentication schemes. | ||
1291 | 1248 | ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not | ||
1292 | 1249 | (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")), | ||
1293 | 1250 | GetFileContents(filepath).split('\n'))) + "PasswordAuthentication no\nChallengeResponseAuthentication no\n") | ||
1294 | 1251 | Log("Disabled SSH password-based authentication methods.") | ||
1295 | 1252 | if self.AdminPassword != None: | ||
1296 | 1253 | os.popen("chpasswd", "w").write("root:" + self.AdminPassword + "\n") | ||
1297 | 1254 | if self.UserName != None: | ||
1298 | 1255 | error = CreateAccount(self.UserName, self.UserPassword, None, None) | ||
1299 | 1256 | sel = os.popen("getenforce").read().startswith("Enforcing") | ||
1300 | 1257 | if sel == True and IsRedHat(): | ||
1301 | 1258 | Run("setenforce 0") | ||
1302 | 1259 | home = GetHome() | ||
1303 | 1260 | for pkey in self.SshPublicKeys: | ||
1304 | 1261 | if not os.path.isfile(pkey[0] + ".crt"): | ||
1305 | 1262 | Error("PublicKey not found: " + pkey[0]) | ||
1306 | 1263 | error = "Failed to deploy public key (0x09)." | ||
1307 | 1264 | continue | ||
1308 | 1265 | path = self.PrepareDir(pkey[1]) | ||
1309 | 1266 | if path == None: | ||
1310 | 1267 | Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0]) | ||
1311 | 1268 | error = "Invalid path for public key (0x03)." | ||
1312 | 1269 | continue | ||
1313 | 1270 | Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub") | ||
1314 | 1271 | if IsRedHat(): | ||
1315 | 1272 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + pkey[0] + ".pub") | ||
1316 | 1273 | if IsUbuntu(): | ||
1317 | 1274 | # Only supported in new SSH releases | ||
1318 | 1275 | Run("ssh-keygen -i -m PKCS8 -f " + pkey[0] + ".pub >> " + path) | ||
1319 | 1276 | else: | ||
1320 | 1277 | SshPubKey = self.OpensslToSsh(pkey[0] + ".pub") | ||
1321 | 1278 | if SshPubKey != None: | ||
1322 | 1279 | AppendFileContents(path, SshPubKey) | ||
1323 | 1280 | else: | ||
1324 | 1281 | Error("Failed: " + pkey[0] + ".crt -> " + path) | ||
1325 | 1282 | error = "Failed to deploy public key (0x04)." | ||
1326 | 1283 | if IsRedHat(): | ||
1327 | 1284 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path) | ||
1328 | 1285 | if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")): | ||
1329 | 1286 | ChangeOwner(path, self.UserName) | ||
1330 | 1287 | for keyp in self.SshKeyPairs: | ||
1331 | 1288 | if not os.path.isfile(keyp[0] + ".prv"): | ||
1332 | 1289 | Error("KeyPair not found: " + keyp[0]) | ||
1333 | 1290 | error = "Failed to deploy key pair (0x0A)." | ||
1334 | 1291 | continue | ||
1335 | 1292 | path = self.PrepareDir(keyp[1]) | ||
1336 | 1293 | if path == None: | ||
1337 | 1294 | Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0]) | ||
1338 | 1295 | error = "Invalid path for key pair (0x05)." | ||
1339 | 1296 | continue | ||
1340 | 1297 | SetFileContents(path, GetFileContents(keyp[0] + ".prv")) | ||
1341 | 1298 | os.chmod(path, 0600) | ||
1342 | 1299 | Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub") | ||
1343 | 1300 | if IsRedHat(): | ||
1344 | 1301 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path) | ||
1345 | 1302 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path + ".pub") | ||
1346 | 1303 | if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")): | ||
1347 | 1304 | ChangeOwner(path, self.UserName) | ||
1348 | 1305 | ChangeOwner(path + ".pub", self.UserName) | ||
1349 | 1306 | if sel == True and IsRedHat(): | ||
1350 | 1307 | Run("setenforce 1") | ||
1351 | 1308 | while not WaAgent.EnvMonitor.IsNamePublished(): | ||
1352 | 1309 | time.sleep(1) | ||
1353 | 1310 | ReloadSshd() | ||
1354 | 1311 | return error | ||
1355 | 1312 | |||
1356 | 1313 | def UpdateAndPublishHostNameCommon(name): | ||
1357 | 1314 | # RedHat | ||
1358 | 1315 | if IsRedHat(): | ||
1359 | 1316 | filepath = "/etc/sysconfig/network" | ||
1360 | 1317 | if os.path.isfile(filepath): | ||
1361 | 1318 | ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n" | ||
1362 | 1319 | + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n')))) | ||
1363 | 1320 | |||
1364 | 1321 | for ethernetInterface in PossibleEthernetInterfaces: | ||
1365 | 1322 | filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface | ||
1366 | 1323 | if os.path.isfile(filepath): | ||
1367 | 1324 | ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n" | ||
1368 | 1325 | + "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n')))) | ||
1369 | 1326 | |||
1370 | 1327 | # Debian | ||
1371 | 1328 | if IsDebian(): | ||
1372 | 1329 | SetFileContents("/etc/hostname", name) | ||
1373 | 1330 | |||
1374 | 1331 | for filepath in EtcDhcpClientConfFiles: | ||
1375 | 1332 | if os.path.isfile(filepath): | ||
1376 | 1333 | ReplaceFileContentsAtomic(filepath, "send host-name \"" + name + "\";\n" | ||
1377 | 1334 | + "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents(filepath).split('\n')))) | ||
1378 | 1335 | |||
1379 | 1336 | # Suse | ||
1380 | 1337 | if IsSuse(): | ||
1381 | 1338 | SetFileContents("/etc/HOSTNAME", name) | ||
1382 | 1339 | |||
1383 | 1340 | class Agent(Util): | ||
1384 | 1341 | def __init__(self): | ||
1385 | 1342 | self.GoalState = None | ||
1386 | 1343 | self.Endpoint = None | ||
1387 | 1344 | self.LoadBalancerProbeServer = None | ||
1388 | 1345 | self.HealthReportCounter = 0 | ||
1389 | 1346 | self.TransportCert = "" | ||
1390 | 1347 | self.EnvMonitor = None | ||
1391 | 1348 | self.SendData = None | ||
1392 | 1349 | self.DhcpResponse = None | ||
1393 | 1350 | |||
1394 | 1351 | def CheckVersions(self): | ||
1395 | 1352 | #<?xml version="1.0" encoding="utf-8"?> | ||
1396 | 1353 | #<Versions> | ||
1397 | 1354 | # <Preferred> | ||
1398 | 1355 | # <Version>2010-12-15</Version> | ||
1399 | 1356 | # </Preferred> | ||
1400 | 1357 | # <Supported> | ||
1401 | 1358 | # <Version>2010-12-15</Version> | ||
1402 | 1359 | # <Version>2010-28-10</Version> | ||
1403 | 1360 | # </Supported> | ||
1404 | 1361 | #</Versions> | ||
1405 | 1362 | global ProtocolVersion | ||
1406 | 1363 | protocolVersionSeen = False | ||
1407 | 1364 | node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0] | ||
1408 | 1365 | if node.localName != "Versions": | ||
1409 | 1366 | Error("CheckVersions: root not Versions") | ||
1410 | 1367 | return False | ||
1411 | 1368 | for a in node.childNodes: | ||
1412 | 1369 | if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported": | ||
1413 | 1370 | for b in a.childNodes: | ||
1414 | 1371 | if b.nodeType == node.ELEMENT_NODE and b.localName == "Version": | ||
1415 | 1372 | v = GetNodeTextData(b) | ||
1416 | 1373 | LogIfVerbose("Fabric supported wire protocol version: " + v) | ||
1417 | 1374 | if v == ProtocolVersion: | ||
1418 | 1375 | protocolVersionSeen = True | ||
1419 | 1376 | if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred": | ||
1420 | 1377 | v = GetNodeTextData(a.getElementsByTagName("Version")[0]) | ||
1421 | 1378 | LogIfVerbose("Fabric preferred wire protocol version: " + v) | ||
1422 | 1379 | if ProtocolVersion < v: | ||
1423 | 1380 | Warn("Newer wire protocol version detected. Please consider updating waagent.") | ||
1424 | 1381 | if not protocolVersionSeen: | ||
1425 | 1382 | Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.") | ||
1426 | 1383 | ProtocolVersion = "2011-08-31" | ||
1427 | 1384 | Log("Negotiated wire protocol version: " + ProtocolVersion) | ||
1428 | 1385 | return True | ||
1429 | 1386 | |||
1430 | 1387 | def Unpack(self, buffer, offset, range): | ||
1431 | 1388 | result = 0 | ||
1432 | 1389 | for i in range: | ||
1433 | 1390 | result = (result << 8) | Ord(buffer[offset + i]) | ||
1434 | 1391 | return result | ||
1435 | 1392 | |||
1436 | 1393 | def UnpackLittleEndian(self, buffer, offset, length): | ||
1437 | 1394 | return self.Unpack(buffer, offset, range(length - 1, -1, -1)) | ||
1438 | 1395 | |||
1439 | 1396 | def UnpackBigEndian(self, buffer, offset, length): | ||
1440 | 1397 | return self.Unpack(buffer, offset, range(0, length)) | ||
1441 | 1398 | |||
1442 | 1399 | def HexDump3(self, buffer, offset, length): | ||
1443 | 1400 | return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]]) | ||
1444 | 1401 | |||
1445 | 1402 | def HexDump2(self, buffer): | ||
1446 | 1403 | return self.HexDump3(buffer, 0, len(buffer)) | ||
1447 | 1404 | |||
1448 | 1405 | def BuildDhcpRequest(self): | ||
1449 | 1406 | # | ||
1450 | 1407 | # typedef struct _DHCP { | ||
1451 | 1408 | # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */ | ||
1452 | 1409 | # UINT8 HardwareAddressType; /* htype: ethernet */ | ||
1453 | 1410 | # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */ | ||
1454 | 1411 | # UINT8 Hops; /* hops: 0 */ | ||
1455 | 1412 | # UINT8 TransactionID[4]; /* xid: random */ | ||
1456 | 1413 | # UINT8 Seconds[2]; /* secs: 0 */ | ||
1457 | 1414 | # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */ | ||
1458 | 1415 | # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */ | ||
1459 | 1416 | # UINT8 YourIpAddress[4]; /* yiaddr: 0 */ | ||
1460 | 1417 | # UINT8 ServerIpAddress[4]; /* siaddr: 0 */ | ||
1461 | 1418 | # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */ | ||
1462 | 1419 | # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */ | ||
1463 | 1420 | # UINT8 ServerName[64]; /* sname: 0 */ | ||
1464 | 1421 | # UINT8 BootFileName[128]; /* file: 0 */ | ||
1465 | 1422 | # UINT8 MagicCookie[4]; /* 99 130 83 99 */ | ||
1466 | 1423 | # /* 0x63 0x82 0x53 0x63 */ | ||
1467 | 1424 | # /* options -- hard code ours */ | ||
1468 | 1425 | # | ||
1469 | 1426 | # UINT8 MessageTypeCode; /* 53 */ | ||
1470 | 1427 | # UINT8 MessageTypeLength; /* 1 */ | ||
1471 | 1428 | # UINT8 MessageType; /* 1 for DISCOVER */ | ||
1472 | 1429 | # UINT8 End; /* 255 */ | ||
1473 | 1430 | # } DHCP; | ||
1474 | 1431 | # | ||
1475 | 1432 | |||
1476 | 1433 | # tuple of 244 zeros | ||
1477 | 1434 | # (struct.pack_into would be good here, but requires Python 2.5) | ||
1478 | 1435 | sendData = [0] * 244 | ||
1479 | 1436 | |||
1480 | 1437 | transactionID = os.urandom(4) | ||
1481 | 1438 | macAddress = GetMacAddress() | ||
1482 | 1439 | |||
1483 | 1440 | # Opcode = 1 | ||
1484 | 1441 | # HardwareAddressType = 1 (ethernet/MAC) | ||
1485 | 1442 | # HardwareAddressLength = 6 (ethernet/MAC/48 bits) | ||
1486 | 1443 | for a in range(0, 3): | ||
1487 | 1444 | sendData[a] = [1, 1, 6][a] | ||
1488 | 1445 | |||
1489 | 1446 | # fill in transaction id (random number to ensure response matches request) | ||
1490 | 1447 | for a in range(0, 4): | ||
1491 | 1448 | sendData[4 + a] = Ord(transactionID[a]) | ||
1492 | 1449 | |||
1493 | 1450 | LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4))) | ||
1494 | 1451 | |||
1495 | 1452 | # fill in ClientHardwareAddress | ||
1496 | 1453 | for a in range(0, 6): | ||
1497 | 1454 | sendData[0x1C + a] = Ord(macAddress[a]) | ||
1498 | 1455 | |||
1499 | 1456 | # DHCP Magic Cookie: 99, 130, 83, 99 | ||
1500 | 1457 | # MessageTypeCode = 53 DHCP Message Type | ||
1501 | 1458 | # MessageTypeLength = 1 | ||
1502 | 1459 | # MessageType = DHCPDISCOVER | ||
1503 | 1460 | # End = 255 DHCP_END | ||
1504 | 1461 | for a in range(0, 8): | ||
1505 | 1462 | sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a] | ||
1506 | 1463 | return array.array("c", map(chr, sendData)) | ||
1507 | 1464 | |||
1508 | 1465 | def IntegerToIpAddressV4String(self, a): | ||
1509 | 1466 | return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF) | ||
1510 | 1467 | |||
1511 | 1468 | def RouteAdd(self, net, mask, gateway): | ||
1512 | 1469 | if IsWindows(): | ||
1513 | 1470 | return | ||
1514 | 1471 | net = self.IntegerToIpAddressV4String(net) | ||
1515 | 1472 | mask = self.IntegerToIpAddressV4String(mask) | ||
1516 | 1473 | gateway = self.IntegerToIpAddressV4String(gateway) | ||
1517 | 1474 | Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway) | ||
1518 | 1475 | |||
1519 | 1476 | def HandleDhcpResponse(self, sendData, receiveBuffer): | ||
1520 | 1477 | LogIfVerbose("HandleDhcpResponse") | ||
1521 | 1478 | bytesReceived = len(receiveBuffer) | ||
1522 | 1479 | if bytesReceived < 0xF6: | ||
1523 | 1480 | Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived)) | ||
1524 | 1481 | return None | ||
1525 | 1482 | |||
1526 | 1483 | LogIfVerbose("BytesReceived: " + hex(bytesReceived)) | ||
1527 | 1484 | LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived)) | ||
1528 | 1485 | |||
1529 | 1486 | # check transactionId, cookie, MAC address | ||
1530 | 1487 | # cookie should never mismatch | ||
1531 | 1488 | # transactionId and MAC address may mismatch if we see a response meant from another machine | ||
1532 | 1489 | |||
1533 | 1490 | for offsets in [range(4, 4 + 4), range(0x1C, 0x1C + 6), range(0xEC, 0xEC + 4)]: | ||
1534 | 1491 | for offset in offsets: | ||
1535 | 1492 | sentByte = Ord(sendData[offset]) | ||
1536 | 1493 | receivedByte = Ord(receiveBuffer[offset]) | ||
1537 | 1494 | if sentByte != receivedByte: | ||
1538 | 1495 | LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4)) | ||
1539 | 1496 | LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4)) | ||
1540 | 1497 | LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4)) | ||
1541 | 1498 | LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4)) | ||
1542 | 1499 | LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6)) | ||
1543 | 1500 | LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6)) | ||
1544 | 1501 | LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch") | ||
1545 | 1502 | return None | ||
1546 | 1503 | endpoint = None | ||
1547 | 1504 | |||
1548 | 1505 | # | ||
1549 | 1506 | # Walk all the returned options, parsing out what we need, ignoring the others. | ||
1550 | 1507 | # We need the custom option 245 to find the the endpoint we talk to, | ||
1551 | 1508 | # as well as, to handle some Linux DHCP client incompatibilities, | ||
1552 | 1509 | # options 3 for default gateway and 249 for routes. And 255 is end. | ||
1553 | 1510 | # | ||
1554 | 1511 | |||
1555 | 1512 | i = 0xF0 # offset to first option | ||
1556 | 1513 | while i < bytesReceived: | ||
1557 | 1514 | option = Ord(receiveBuffer[i]) | ||
1558 | 1515 | length = 0 | ||
1559 | 1516 | if (i + 1) < bytesReceived: | ||
1560 | 1517 | length = Ord(receiveBuffer[i + 1]) | ||
1561 | 1518 | LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length)) | ||
1562 | 1519 | if option == 255: | ||
1563 | 1520 | LogIfVerbose("DHCP packet ended at offset " + hex(i)) | ||
1564 | 1521 | break | ||
1565 | 1522 | elif option == 249: | ||
1566 | 1523 | # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx | ||
1567 | 1524 | LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length)) | ||
1568 | 1525 | if length < 5: | ||
1569 | 1526 | Error("Data too small for option " + option) | ||
1570 | 1527 | j = i + 2 | ||
1571 | 1528 | while j < (i + length + 2): | ||
1572 | 1529 | maskLengthBits = Ord(receiveBuffer[j]) | ||
1573 | 1530 | maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3) | ||
1574 | 1531 | mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits)) | ||
1575 | 1532 | j += 1 | ||
1576 | 1533 | net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes) | ||
1577 | 1534 | net <<= (32 - maskLengthBytes * 8) | ||
1578 | 1535 | net &= mask | ||
1579 | 1536 | j += maskLengthBytes | ||
1580 | 1537 | gateway = self.UnpackBigEndian(receiveBuffer, j, 4) | ||
1581 | 1538 | j += 4 | ||
1582 | 1539 | self.RouteAdd(net, mask, gateway) | ||
1583 | 1540 | if j != (i + length + 2): | ||
1584 | 1541 | Error("HandleDhcpResponse: Unable to parse routes") | ||
1585 | 1542 | elif option == 3 or option == 245: | ||
1586 | 1543 | if i + 5 < bytesReceived: | ||
1587 | 1544 | if length != 4: | ||
1588 | 1545 | Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes") | ||
1589 | 1546 | return None | ||
1590 | 1547 | gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4) | ||
1591 | 1548 | IpAddress = self.IntegerToIpAddressV4String(gateway) | ||
1592 | 1549 | if option == 3: | ||
1593 | 1550 | self.RouteAdd(0, 0, gateway) | ||
1594 | 1551 | name = "DefaultGateway" | ||
1595 | 1552 | else: | ||
1596 | 1553 | endpoint = IpAddress | ||
1597 | 1554 | name = "Windows Azure wire protocol endpoint" | ||
1598 | 1555 | LogIfVerbose(name + ": " + IpAddress + " at " + hex(i)) | ||
1599 | 1556 | else: | ||
1600 | 1557 | Error("HandleDhcpResponse: Data too small for option " + option) | ||
1601 | 1558 | else: | ||
1602 | 1559 | LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length)) | ||
1603 | 1560 | i += length + 2 | ||
1604 | 1561 | return endpoint | ||
1605 | 1562 | |||
1606 | 1563 | def DoDhcpWork(self): | ||
1607 | 1564 | # | ||
1608 | 1565 | # Discover the wire server via DHCP option 245. | ||
1609 | 1566 | # And workaround incompatibility with Windows Azure DHCP servers. | ||
1610 | 1567 | # | ||
1611 | 1568 | ShortSleep = False # Sleep 1 second before retrying DHCP queries. | ||
1612 | 1569 | |||
1613 | 1570 | if not IsWindows(): | ||
1614 | 1571 | Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT") | ||
1615 | 1572 | Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT") | ||
1616 | 1573 | |||
1617 | 1574 | sleepDurations = [0, 5, 10, 30, 60, 60, 60, 60] | ||
1618 | 1575 | maxRetry = len(sleepDurations) | ||
1619 | 1576 | lastTry = (maxRetry - 1) | ||
1620 | 1577 | for retry in range(0, maxRetry): | ||
1621 | 1578 | try: | ||
1622 | 1579 | strRetry = str(retry) | ||
1623 | 1580 | prefix = "DoDhcpWork: try=" + strRetry | ||
1624 | 1581 | LogIfVerbose(prefix) | ||
1625 | 1582 | sendData = self.BuildDhcpRequest() | ||
1626 | 1583 | LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData))) | ||
1627 | 1584 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) | ||
1628 | 1585 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) | ||
1629 | 1586 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
1630 | 1587 | missingDefaultRoute = True | ||
1631 | 1588 | try: | ||
1632 | 1589 | for line in os.popen("route -n").read().split('\n'): | ||
1633 | 1590 | if line.startswith("0.0.0.0 "): | ||
1634 | 1591 | missingDefaultRoute = False | ||
1635 | 1592 | except: | ||
1636 | 1593 | pass | ||
1637 | 1594 | if missingDefaultRoute: | ||
1638 | 1595 | # This is required because sending after binding to 0.0.0.0 fails with | ||
1639 | 1596 | # network unreachable when the default gateway is not set up. | ||
1640 | 1597 | sock.bind((GetIpv4Address(), 68)) | ||
1641 | 1598 | else: | ||
1642 | 1599 | sock.bind(("0.0.0.0", 68)) | ||
1643 | 1600 | sock.sendto(sendData, ("<broadcast>", 67)) | ||
1644 | 1601 | sock.settimeout(30) | ||
1645 | 1602 | LogIfVerbose("DoDhcpWork: Setting socket.timeout=10, entering recv") | ||
1646 | 1603 | receiveBuffer = sock.recv(1024) | ||
1647 | 1604 | sock.close() | ||
1648 | 1605 | endpoint = self.HandleDhcpResponse(sendData, receiveBuffer) | ||
1649 | 1606 | if endpoint == None: | ||
1650 | 1607 | LogIfVerbose("DoDhcpWork: No endpoint found") | ||
1651 | 1608 | if endpoint != None or retry == lastTry: | ||
1652 | 1609 | if endpoint != None: | ||
1653 | 1610 | self.SendData = sendData | ||
1654 | 1611 | self.DhcpResponse = receiveBuffer | ||
1655 | 1612 | if retry == lastTry: | ||
1656 | 1613 | LogIfVerbose("DoDhcpWork: try=" + strRetry) | ||
1657 | 1614 | return endpoint | ||
1658 | 1615 | sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep] | ||
1659 | 1616 | LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration)) | ||
1660 | 1617 | time.sleep(sleepDuration) | ||
1661 | 1618 | except Exception, e: | ||
1662 | 1619 | ErrorWithPrefix(prefix, str(e)) | ||
1663 | 1620 | ErrorWithPrefix(prefix, traceback.format_exc()) | ||
1664 | 1621 | return None | ||
1665 | 1622 | |||
1666 | 1623 | def UpdateAndPublishHostName(self, name): | ||
1667 | 1624 | # Set hostname locally and publish to iDNS | ||
1668 | 1625 | Log("Setting host name: " + name) | ||
1669 | 1626 | UpdateAndPublishHostNameCommon(name) | ||
1670 | 1627 | for ethernetInterface in PossibleEthernetInterfaces: | ||
1671 | 1628 | Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface) | ||
1672 | 1629 | self.RestoreRoutes() | ||
1673 | 1630 | |||
1674 | 1631 | def RestoreRoutes(self): | ||
1675 | 1632 | if self.SendData != None and self.DhcpResponse != None: | ||
1676 | 1633 | self.HandleDhcpResponse(self.SendData, self.DhcpResponse) | ||
1677 | 1634 | |||
1678 | 1635 | def UpdateGoalState(self): | ||
1679 | 1636 | goalStateXml = None | ||
1680 | 1637 | maxRetry = 9 | ||
1681 | 1638 | log = NoLog | ||
1682 | 1639 | for retry in range(1, maxRetry + 1): | ||
1683 | 1640 | strRetry = str(retry) | ||
1684 | 1641 | log("retry UpdateGoalState,retry=" + strRetry) | ||
1685 | 1642 | goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate") | ||
1686 | 1643 | if goalStateXml != None: | ||
1687 | 1644 | break | ||
1688 | 1645 | log = Log | ||
1689 | 1646 | time.sleep(retry) | ||
1690 | 1647 | if not goalStateXml: | ||
1691 | 1648 | Error("UpdateGoalState failed.") | ||
1692 | 1649 | return | ||
1693 | 1650 | Log("Retrieved GoalState from Windows Azure Fabric.") | ||
1694 | 1651 | self.GoalState = GoalState(self).Parse(goalStateXml) | ||
1695 | 1652 | return self.GoalState | ||
1696 | 1653 | |||
1697 | 1654 | def ReportReady(self): | ||
1698 | 1655 | counter = (self.HealthReportCounter + 1) % 1000000 | ||
1699 | 1656 | self.HealthReportCounter = counter | ||
1700 | 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>" | ||
1701 | 1658 | + self.GoalState.Incarnation | ||
1702 | 1659 | + "</GoalStateIncarnation><Container><ContainerId>" | ||
1703 | 1660 | + self.GoalState.ContainerId | ||
1704 | 1661 | + "</ContainerId><RoleInstanceList><Role><InstanceId>" | ||
1705 | 1662 | + self.GoalState.RoleInstanceId | ||
1706 | 1663 | + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>") | ||
1707 | 1664 | a = self.HttpPost("/machine?comp=health", healthReport) | ||
1708 | 1665 | if a != None: | ||
1709 | 1666 | return a.getheader("x-ms-latest-goal-state-incarnation-number") | ||
1710 | 1667 | return None | ||
1711 | 1668 | |||
1712 | 1669 | def ReportNotReady(self, status, desc): | ||
1713 | 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>" | ||
1714 | 1671 | + self.GoalState.Incarnation | ||
1715 | 1672 | + "</GoalStateIncarnation><Container><ContainerId>" | ||
1716 | 1673 | + self.GoalState.ContainerId | ||
1717 | 1674 | + "</ContainerId><RoleInstanceList><Role><InstanceId>" | ||
1718 | 1675 | + self.GoalState.RoleInstanceId | ||
1719 | 1676 | + "</InstanceId><Health><State>NotReady</State>" | ||
1720 | 1677 | + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>" | ||
1721 | 1678 | + "</Health></Role></RoleInstanceList></Container></Health>") | ||
1722 | 1679 | a = self.HttpPost("/machine?comp=health", healthReport) | ||
1723 | 1680 | if a != None: | ||
1724 | 1681 | return a.getheader("x-ms-latest-goal-state-incarnation-number") | ||
1725 | 1682 | return None | ||
1726 | 1683 | |||
1727 | 1684 | def ReportRoleProperties(self, thumbprint): | ||
1728 | 1685 | roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>" | ||
1729 | 1686 | + "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>" | ||
1730 | 1687 | + "<RoleInstances><RoleInstance>" | ||
1731 | 1688 | + "<Id>" + self.GoalState.RoleInstanceId + "</Id>" | ||
1732 | 1689 | + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>" | ||
1733 | 1690 | + "</RoleInstance></RoleInstances></Container></RoleProperties>") | ||
1734 | 1691 | a = self.HttpPost("/machine?comp=roleProperties", roleProperties) | ||
1735 | 1692 | Log("Posted Role Properties. CertificateThumbprint=" + thumbprint) | ||
1736 | 1693 | return a | ||
1737 | 1694 | |||
1738 | 1695 | def LoadBalancerProbeServer_Shutdown(self): | ||
1739 | 1696 | if self.LoadBalancerProbeServer != None: | ||
1740 | 1697 | self.LoadBalancerProbeServer.shutdown() | ||
1741 | 1698 | self.LoadBalancerProbeServer = None | ||
1742 | 1699 | |||
1743 | 1700 | def GenerateTransportCert(self): | ||
1744 | 1701 | Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem") | ||
1745 | 1702 | cert = "" | ||
1746 | 1703 | for line in GetFileContents("TransportCert.pem").split('\n'): | ||
1747 | 1704 | if not "CERTIFICATE" in line: | ||
1748 | 1705 | cert += line.rstrip() | ||
1749 | 1706 | return cert | ||
1750 | 1707 | |||
1751 | 1708 | def Provision(self): | ||
1752 | 1709 | if IsWindows(): | ||
1753 | 1710 | Log("Skipping Provision on Windows") | ||
1754 | 1711 | return | ||
1755 | 1712 | enabled = Config.get("Provisioning.Enabled") | ||
1756 | 1713 | if enabled != None and enabled.lower().startswith("n"): | ||
1757 | 1714 | return | ||
1758 | 1715 | Log("Provisioning image started.") | ||
1759 | 1716 | type = Config.get("Provisioning.SshHostKeyPairType") | ||
1760 | 1717 | if type == None: | ||
1761 | 1718 | type = "rsa" | ||
1762 | 1719 | regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair") | ||
1763 | 1720 | if regenerateKeys == None or regenerateKeys.lower().startswith("y"): | ||
1764 | 1721 | Run("rm -f /etc/ssh/ssh_host_*key*") | ||
1765 | 1722 | Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key") | ||
1766 | 1723 | ReloadSshd() | ||
1767 | 1724 | SetFileContents(LibDir + "/provisioned", "") | ||
1768 | 1725 | dvd = "/dev/hdc" | ||
1769 | 1726 | if os.path.exists("/dev/sr0"): | ||
1770 | 1727 | dvd = "/dev/sr0" | ||
1771 | 1728 | if Run("fdisk -l " + dvd + " | grep Disk"): | ||
1772 | 1729 | return | ||
1773 | 1730 | CreateDir("/mnt/cdrom/secure", "root", 0700) | ||
1774 | 1731 | if Run("mount " + dvd + " /mnt/cdrom/secure"): | ||
1775 | 1732 | Error("Unable to provision: Failed to mount DVD.") | ||
1776 | 1733 | return "Failed to retrieve provisioning data (0x01)." | ||
1777 | 1734 | if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"): | ||
1778 | 1735 | Error("Unable to provision: Missing ovf-env.xml on DVD.") | ||
1779 | 1736 | return "Failed to retrieve provisioning data (0x02)." | ||
1780 | 1737 | ovfxml = GetFileContents("/mnt/cdrom/secure/ovf-env.xml") | ||
1781 | 1738 | SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml)) | ||
1782 | 1739 | Run("umount /mnt/cdrom/secure") | ||
1783 | 1740 | error = None | ||
1784 | 1741 | if ovfxml != None: | ||
1785 | 1742 | Log("Provisioning image using OVF settings in the DVD.") | ||
1786 | 1743 | ovfobj = OvfEnv().Parse(ovfxml) | ||
1787 | 1744 | if ovfobj != None: | ||
1788 | 1745 | error = ovfobj.Process() | ||
1789 | 1746 | # This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml | ||
1790 | 1747 | fingerprint = os.popen("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub").read().rstrip().split()[1].replace(':','') | ||
1791 | 1748 | self.ReportRoleProperties(fingerprint) | ||
1792 | 1749 | delRootPass = Config.get("Provisioning.DeleteRootPassword") | ||
1793 | 1750 | if delRootPass != None and delRootPass.lower().startswith("y"): | ||
1794 | 1751 | DeleteRootPassword() | ||
1795 | 1752 | Log("Provisioning image completed.") | ||
1796 | 1753 | return error | ||
1797 | 1754 | |||
1798 | 1755 | def Run(self): | ||
1799 | 1756 | if IsLinux(): | ||
1800 | 1757 | SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n") | ||
1801 | 1758 | |||
1802 | 1759 | if GetIpv4Address() == None: | ||
1803 | 1760 | Log("Waiting for network.") | ||
1804 | 1761 | while(GetIpv4Address() == None): | ||
1805 | 1762 | time.sleep(10) | ||
1806 | 1763 | |||
1807 | 1764 | Log("IPv4 address: " + GetIpv4Address()) | ||
1808 | 1765 | Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in GetMacAddress()])) | ||
1809 | 1766 | |||
1810 | 1767 | # Consume Entropy in ACPI table provided by Hyper-V | ||
1811 | 1768 | try: | ||
1812 | 1769 | SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0")) | ||
1813 | 1770 | except: | ||
1814 | 1771 | pass | ||
1815 | 1772 | |||
1816 | 1773 | Log("Probing for Windows Azure environment.") | ||
1817 | 1774 | self.Endpoint = self.DoDhcpWork() | ||
1818 | 1775 | |||
1819 | 1776 | if self.Endpoint == None: | ||
1820 | 1777 | Log("Windows Azure environment not detected.") | ||
1821 | 1778 | while True: | ||
1822 | 1779 | time.sleep(60) | ||
1823 | 1780 | |||
1824 | 1781 | Log("Discovered Windows Azure endpoint: " + self.Endpoint) | ||
1825 | 1782 | if not self.CheckVersions(): | ||
1826 | 1783 | Error("Agent.CheckVersions failed") | ||
1827 | 1784 | sys.exit(1) | ||
1828 | 1785 | |||
1829 | 1786 | self.EnvMonitor = EnvMonitor() | ||
1830 | 1787 | |||
1831 | 1788 | # Set SCSI timeout on root device | ||
1832 | 1789 | try: | ||
1833 | 1790 | timeout = Config.get("OS.RootDeviceScsiTimeout") | ||
1834 | 1791 | if timeout != None: | ||
1835 | 1792 | SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout) | ||
1836 | 1793 | except: | ||
1837 | 1794 | pass | ||
1838 | 1795 | |||
1839 | 1796 | global Openssl | ||
1840 | 1797 | Openssl = Config.get("OS.OpensslPath") | ||
1841 | 1798 | if Openssl == None: | ||
1842 | 1799 | Openssl = "openssl" | ||
1843 | 1800 | |||
1844 | 1801 | self.TransportCert = self.GenerateTransportCert() | ||
1845 | 1802 | |||
1846 | 1803 | incarnation = None # goalStateIncarnationFromHealthReport | ||
1847 | 1804 | currentPort = None # loadBalancerProbePort | ||
1848 | 1805 | goalState = None # self.GoalState, instance of GoalState | ||
1849 | 1806 | provisioned = os.path.exists(LibDir + "/provisioned") | ||
1850 | 1807 | program = Config.get("Role.StateConsumer") | ||
1851 | 1808 | provisionError = None | ||
1852 | 1809 | lbProbeResponder = True | ||
1853 | 1810 | setting = Config.get("LBProbeResponder") | ||
1854 | 1811 | if setting != None and setting.lower().startswith("n"): | ||
1855 | 1812 | lbProbeResponder = False | ||
1856 | 1813 | while True: | ||
1857 | 1814 | if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation): | ||
1858 | 1815 | goalState = self.UpdateGoalState() | ||
1859 | 1816 | |||
1860 | 1817 | if provisioned == False: | ||
1861 | 1818 | self.ReportNotReady("Provisioning", "Starting") | ||
1862 | 1819 | |||
1863 | 1820 | goalState.Process() | ||
1864 | 1821 | |||
1865 | 1822 | if provisioned == False: | ||
1866 | 1823 | provisionError = self.Provision() | ||
1867 | 1824 | provisioned = True | ||
1868 | 1825 | |||
1869 | 1826 | # | ||
1870 | 1827 | # only one port supported | ||
1871 | 1828 | # restart server if new port is different than old port | ||
1872 | 1829 | # stop server if no longer a port | ||
1873 | 1830 | # | ||
1874 | 1831 | goalPort = goalState.LoadBalancerProbePort | ||
1875 | 1832 | if currentPort != goalPort: | ||
1876 | 1833 | self.LoadBalancerProbeServer_Shutdown() | ||
1877 | 1834 | currentPort = goalPort | ||
1878 | 1835 | if currentPort != None and lbProbeResponder == True: | ||
1879 | 1836 | self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort) | ||
1880 | 1837 | |||
1881 | 1838 | if program != None and DiskActivated == True: | ||
1882 | 1839 | Children.append(subprocess.Popen([program, "Ready"])) | ||
1883 | 1840 | program = None | ||
1884 | 1841 | |||
1885 | 1842 | if goalState.ExpectedState == "Stopped": | ||
1886 | 1843 | program = Config.get("Role.StateConsumer") | ||
1887 | 1844 | if program != None: | ||
1888 | 1845 | Run(program + " Shutdown") | ||
1889 | 1846 | self.EnvMonitor.ShutdownService() | ||
1890 | 1847 | self.LoadBalancerProbeServer_Shutdown() | ||
1891 | 1848 | command = ["/sbin/shutdown -hP now", "shutdown /s /t 5"][IsWindows()] | ||
1892 | 1849 | Run(command) | ||
1893 | 1850 | return | ||
1894 | 1851 | |||
1895 | 1852 | sleepToReduceAccessDenied = 3 | ||
1896 | 1853 | time.sleep(sleepToReduceAccessDenied) | ||
1897 | 1854 | if provisionError != None: | ||
1898 | 1855 | incarnation = self.ReportNotReady("ProvisioningFailed", provisionError) | ||
1899 | 1856 | else: | ||
1900 | 1857 | incarnation = self.ReportReady() | ||
1901 | 1858 | time.sleep(25 - sleepToReduceAccessDenied) | ||
1902 | 1859 | |||
1903 | 1860 | Init_Suse = """\ | ||
1904 | 1861 | #! /bin/sh | ||
1905 | 1862 | |||
1906 | 1863 | ### BEGIN INIT INFO | ||
1907 | 1864 | # Provides: WindowsAzureLinuxAgent | ||
1908 | 1865 | # Required-Start: $network sshd | ||
1909 | 1866 | # Required-Stop: $network sshd | ||
1910 | 1867 | # Default-Start: 3 5 | ||
1911 | 1868 | # Default-Stop: 0 1 2 6 | ||
1912 | 1869 | # Description: Start the WindowsAzureLinuxAgent | ||
1913 | 1870 | ### END INIT INFO | ||
1914 | 1871 | |||
1915 | 1872 | WAZD_BIN=/usr/sbin/waagent | ||
1916 | 1873 | test -x $WAZD_BIN || exit 5 | ||
1917 | 1874 | |||
1918 | 1875 | case "$1" in | ||
1919 | 1876 | start) | ||
1920 | 1877 | echo "Starting WindowsAzureLinuxAgent" | ||
1921 | 1878 | ## Start daemon with startproc(8). If this fails | ||
1922 | 1879 | ## the echo return value is set appropriate. | ||
1923 | 1880 | |||
1924 | 1881 | startproc -f $WAZD_BIN -daemon | ||
1925 | 1882 | exit $? | ||
1926 | 1883 | ;; | ||
1927 | 1884 | stop) | ||
1928 | 1885 | echo "Shutting down WindowsAzureLinuxAgent" | ||
1929 | 1886 | ## Stop daemon with killproc(8) and if this fails | ||
1930 | 1887 | ## set echo the echo return value. | ||
1931 | 1888 | |||
1932 | 1889 | killproc -p /var/run/waagent.pid $WAZD_BIN | ||
1933 | 1890 | exit $? | ||
1934 | 1891 | ;; | ||
1935 | 1892 | try-restart) | ||
1936 | 1893 | ## Stop the service and if this succeeds (i.e. the | ||
1937 | 1894 | ## service was running before), start it again. | ||
1938 | 1895 | $0 status >/dev/null && $0 restart | ||
1939 | 1896 | ;; | ||
1940 | 1897 | restart) | ||
1941 | 1898 | ## Stop the service and regardless of whether it was | ||
1942 | 1899 | ## running or not, start it again. | ||
1943 | 1900 | $0 stop | ||
1944 | 1901 | $0 start | ||
1945 | 1902 | ;; | ||
1946 | 1903 | force-reload|reload) | ||
1947 | 1904 | ;; | ||
1948 | 1905 | status) | ||
1949 | 1906 | echo -n "Checking for service WindowsAzureLinuxAgent " | ||
1950 | 1907 | ## Check status with checkproc(8), if process is running | ||
1951 | 1908 | ## checkproc will return with exit status 0. | ||
1952 | 1909 | |||
1953 | 1910 | checkproc -p $WAZD_PIDFILE $WAZD_BIN | ||
1954 | 1911 | exit $? | ||
1955 | 1912 | ;; | ||
1956 | 1913 | probe) | ||
1957 | 1914 | ;; | ||
1958 | 1915 | *) | ||
1959 | 1916 | echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}" | ||
1960 | 1917 | exit 1 | ||
1961 | 1918 | ;; | ||
1962 | 1919 | esac | ||
1963 | 1920 | """ | ||
1964 | 1921 | |||
1965 | 1922 | Init_RedHat = """\ | ||
1966 | 1923 | #!/bin/bash | ||
1967 | 1924 | # | ||
1968 | 1925 | # Init file for WindowsAzureLinuxAgent. | ||
1969 | 1926 | # | ||
1970 | 1927 | # chkconfig: 2345 60 80 | ||
1971 | 1928 | # description: WindowsAzureLinuxAgent | ||
1972 | 1929 | # | ||
1973 | 1930 | |||
1974 | 1931 | # source function library | ||
1975 | 1932 | . /etc/rc.d/init.d/functions | ||
1976 | 1933 | |||
1977 | 1934 | RETVAL=0 | ||
1978 | 1935 | FriendlyName="WindowsAzureLinuxAgent" | ||
1979 | 1936 | WAZD_BIN=/usr/sbin/waagent | ||
1980 | 1937 | |||
1981 | 1938 | start() | ||
1982 | 1939 | { | ||
1983 | 1940 | echo -n $"Starting $FriendlyName: " | ||
1984 | 1941 | $WAZD_BIN -daemon & | ||
1985 | 1942 | } | ||
1986 | 1943 | |||
1987 | 1944 | stop() | ||
1988 | 1945 | { | ||
1989 | 1946 | echo -n $"Stopping $FriendlyName: " | ||
1990 | 1947 | killproc -p /var/run/waagent.pid $WAZD_BIN | ||
1991 | 1948 | RETVAL=$? | ||
1992 | 1949 | echo | ||
1993 | 1950 | return $RETVAL | ||
1994 | 1951 | } | ||
1995 | 1952 | |||
1996 | 1953 | case "$1" in | ||
1997 | 1954 | start) | ||
1998 | 1955 | start | ||
1999 | 1956 | ;; | ||
2000 | 1957 | stop) | ||
2001 | 1958 | stop | ||
2002 | 1959 | ;; | ||
2003 | 1960 | restart) | ||
2004 | 1961 | stop | ||
2005 | 1962 | start | ||
2006 | 1963 | ;; | ||
2007 | 1964 | reload) | ||
2008 | 1965 | ;; | ||
2009 | 1966 | report) | ||
2010 | 1967 | ;; | ||
2011 | 1968 | status) | ||
2012 | 1969 | status $WAZD_BIN | ||
2013 | 1970 | RETVAL=$? | ||
2014 | 1971 | ;; | ||
2015 | 1972 | *) | ||
2016 | 1973 | echo $"Usage: $0 {start|stop|restart|status}" | ||
2017 | 1974 | RETVAL=1 | ||
2018 | 1975 | esac | ||
2019 | 1976 | exit $RETVAL | ||
2020 | 1977 | """ | ||
2021 | 1978 | |||
2022 | 1979 | Init_Debian = """\ | ||
2023 | 1980 | #!/bin/sh | ||
2024 | 1981 | ### BEGIN INIT INFO | ||
2025 | 1982 | # Provides: WindowsAzureLinuxAgent | ||
2026 | 1983 | # Required-Start: $network $syslog | ||
2027 | 1984 | # Required-Stop: $network $syslog | ||
2028 | 1985 | # Should-Start: $network $syslog | ||
2029 | 1986 | # Should-Stop: $network $syslog | ||
2030 | 1987 | # Default-Start: 2 3 4 5 | ||
2031 | 1988 | # Default-Stop: 0 1 6 | ||
2032 | 1989 | # Short-Description: WindowsAzureLinuxAgent | ||
2033 | 1990 | # Description: WindowsAzureLinuxAgent | ||
2034 | 1991 | ### END INIT INFO | ||
2035 | 1992 | |||
2036 | 1993 | . /lib/lsb/init-functions | ||
2037 | 1994 | |||
2038 | 1995 | OPTIONS="-daemon" | ||
2039 | 1996 | WAZD_BIN=/usr/sbin/waagent | ||
2040 | 1997 | WAZD_PID=/var/run/waagent.pid | ||
2041 | 1998 | |||
2042 | 1999 | case "$1" in | ||
2043 | 2000 | start) | ||
2044 | 2001 | log_begin_msg "Starting WindowsAzureLinuxAgent..." | ||
2045 | 2002 | pid=$( pidofproc $WAZD_BIN ) | ||
2046 | 2003 | if [ -n "$pid" ] ; then | ||
2047 | 2004 | log_begin_msg "Already running." | ||
2048 | 2005 | log_end_msg 0 | ||
2049 | 2006 | exit 0 | ||
2050 | 2007 | fi | ||
2051 | 2008 | start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS | ||
2052 | 2009 | log_end_msg $? | ||
2053 | 2010 | ;; | ||
2054 | 2011 | |||
2055 | 2012 | stop) | ||
2056 | 2013 | log_begin_msg "Stopping WindowsAzureLinuxAgent..." | ||
2057 | 2014 | start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID | ||
2058 | 2015 | ret=$? | ||
2059 | 2016 | rm -f $WAZD_PID | ||
2060 | 2017 | log_end_msg $ret | ||
2061 | 2018 | ;; | ||
2062 | 2019 | force-reload) | ||
2063 | 2020 | $0 restart | ||
2064 | 2021 | ;; | ||
2065 | 2022 | restart) | ||
2066 | 2023 | $0 stop | ||
2067 | 2024 | $0 start | ||
2068 | 2025 | ;; | ||
2069 | 2026 | status) | ||
2070 | 2027 | status_of_proc $WAZD_BIN && exit 0 || exit $? | ||
2071 | 2028 | ;; | ||
2072 | 2029 | *) | ||
2073 | 2030 | log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}" | ||
2074 | 2031 | exit 1 | ||
2075 | 2032 | ;; | ||
2076 | 2033 | esac | ||
2077 | 2034 | |||
2078 | 2035 | exit 0 | ||
2079 | 2036 | """ | ||
2080 | 2037 | |||
2081 | 2038 | WaagentConf = """\ | ||
2082 | 2039 | # | ||
2083 | 2040 | # Windows Azure Linux Agent Configuration | ||
2084 | 2041 | # | ||
2085 | 2042 | |||
2086 | 2043 | Role.StateConsumer=None # Specified program is invoked with "Ready" or "Shutdown". | ||
2087 | 2044 | # Shutdown will be initiated only after the program returns. Windows Azure will | ||
2088 | 2045 | # power off the VM if shutdown is not completed within ?? minutes. | ||
2089 | 2046 | Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration. | ||
2090 | 2047 | Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology. | ||
2091 | 2048 | |||
2092 | 2049 | Provisioning.Enabled=y # | ||
2093 | 2050 | Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable. | ||
2094 | 2051 | Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair. | ||
2095 | 2052 | Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa". | ||
2096 | 2053 | Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests. | ||
2097 | 2054 | |||
2098 | 2055 | ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted. | ||
2099 | 2056 | ResourceDisk.Filesystem=ext4 # | ||
2100 | 2057 | ResourceDisk.MountPoint=/mnt/resource # | ||
2101 | 2058 | ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk. | ||
2102 | 2059 | ResourceDisk.SwapSizeMB=0 # Size of the swapfile. | ||
2103 | 2060 | |||
2104 | 2061 | LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure. | ||
2105 | 2062 | |||
2106 | 2063 | Logs.Verbose=n # | ||
2107 | 2064 | |||
2108 | 2065 | OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds. | ||
2109 | 2066 | OS.OpensslPath=None # If "None", the system default version is used. | ||
2110 | 2067 | """ | ||
2111 | 2068 | |||
2112 | 2069 | WaagentLogrotate = """\ | ||
2113 | 2070 | /var/log/waagent.log { | ||
2114 | 2071 | monthly | ||
2115 | 2072 | rotate 6 | ||
2116 | 2073 | notifempty | ||
2117 | 2074 | missingok | ||
2118 | 2075 | } | ||
2119 | 2076 | """ | ||
2120 | 2077 | |||
2121 | 2078 | def AddToLinuxKernelCmdline(options): | ||
2122 | 2079 | if os.path.isfile("/boot/grub/menu.lst"): | ||
2123 | 2080 | Run("sed -i --follow-symlinks '/kernel/s|$| " + options + " |' /boot/grub/menu.lst") | ||
2124 | 2081 | filepath = "/etc/default/grub" | ||
2125 | 2082 | if os.path.isfile(filepath): | ||
2126 | 2083 | filecontents = GetFileContents(filepath).split('\n') | ||
2127 | 2084 | current = filter(lambda a: a.startswith("GRUB_CMDLINE_LINUX"), filecontents) | ||
2128 | 2085 | ReplaceFileContentsAtomic(filepath, | ||
2129 | 2086 | "\n".join(filter(lambda a: not a.startswith("GRUB_CMDLINE_LINUX"), filecontents)) | ||
2130 | 2087 | + current[0][:-1] + " " + options + "\"\n") | ||
2131 | 2088 | Run("update-grub") | ||
2132 | 2089 | |||
2133 | 2090 | def ApplyVNUMAWorkaround(): | ||
2134 | 2091 | VersionParts = platform.release().replace('-', '.').split('.') | ||
2135 | 2092 | if int(VersionParts[0]) > 2: | ||
2136 | 2093 | return | ||
2137 | 2094 | if int(VersionParts[1]) > 6: | ||
2138 | 2095 | return | ||
2139 | 2096 | if int(VersionParts[2]) > 37: | ||
2140 | 2097 | return | ||
2141 | 2098 | AddToLinuxKernelCmdline("numa=off") | ||
2142 | 2099 | # TODO: This is not ideal for offline installation. | ||
2143 | 2100 | print("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.") | ||
2144 | 2101 | |||
2145 | 2102 | def RevertVNUMAWorkaround(): | ||
2146 | 2103 | print("Automatic reverting of GRUB configuration is not yet supported. Please edit by hand.") | ||
2147 | 2104 | |||
2148 | 2105 | def Install(): | ||
2149 | 2106 | if IsWindows(): | ||
2150 | 2107 | print("ERROR: -install invalid for Windows.") | ||
2151 | 2108 | return 1 | ||
2152 | 2109 | os.chmod(sys.argv[0], 0755) | ||
2153 | 2110 | SwitchCwd() | ||
2154 | 2111 | requiredDeps = [ "/sbin/route", "/sbin/shutdown" ] | ||
2155 | 2112 | if IsDebian(): | ||
2156 | 2113 | requiredDeps += [ "/usr/sbin/update-rc.d" ] | ||
2157 | 2114 | if IsSuse(): | ||
2158 | 2115 | requiredDeps += [ "/sbin/insserv" ] | ||
2159 | 2116 | for a in requiredDeps: | ||
2160 | 2117 | if not os.path.isfile(a): | ||
2161 | 2118 | Error("Missing required dependency: " + a) | ||
2162 | 2119 | return 1 | ||
2163 | 2120 | missing = False | ||
2164 | 2121 | for a in [ "ssh-keygen", "useradd", "openssl", "sfdisk", | ||
2165 | 2122 | "fdisk", "mkfs", "chpasswd", "sed", "grep", "sudo" ]: | ||
2166 | 2123 | if Run("which " + a + " > /dev/null 2>&1"): | ||
2167 | 2124 | Warn("Missing dependency: " + a) | ||
2168 | 2125 | missing = True | ||
2169 | 2126 | if missing == True: | ||
2170 | 2127 | Warn("Please resolve missing dependencies listed for full functionality.") | ||
2171 | 2128 | if UsesRpm(): | ||
2172 | 2129 | if not Run("rpm --quiet -q NetworkManager"): | ||
2173 | 2130 | Error(GuestAgentLongName + " is not compatible with NetworkManager.") | ||
2174 | 2131 | return 1 | ||
2175 | 2132 | if Run("rpm --quiet -q python-pyasn1"): | ||
2176 | 2133 | Error(GuestAgentLongName + " requires python-pyasn1.") | ||
2177 | 2134 | return 1 | ||
2178 | 2135 | if UsesDpkg() and Run("dpkg -l network-manager | grep -q ^un"): | ||
2179 | 2136 | Error(GuestAgentLongName + " is not compatible with network-manager.") | ||
2180 | 2137 | return 1 | ||
2181 | 2138 | for a in RulesFiles: | ||
2182 | 2139 | if os.path.isfile(a): | ||
2183 | 2140 | if os.path.isfile(GetLastPathElement(a)): | ||
2184 | 2141 | os.remove(GetLastPathElement(a)) | ||
2185 | 2142 | shutil.move(a, ".") | ||
2186 | 2143 | Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) ) | ||
2187 | 2144 | filename = "waagent" | ||
2188 | 2145 | filepath = "/etc/init.d/" + filename | ||
2189 | 2146 | distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3 | ||
2190 | 2147 | if distro == 0: | ||
2191 | 2148 | Error("Unable to detect Linux Distribution.") | ||
2192 | 2149 | return 1 | ||
2193 | 2150 | init = [[Init_RedHat, "chkconfig --add " + filename], | ||
2194 | 2151 | [Init_Debian, "update-rc.d " + filename + " defaults"], | ||
2195 | 2152 | [Init_Suse, "insserv " + filename]][distro - 1] | ||
2196 | 2153 | SetFileContents(filepath, init[0]) | ||
2197 | 2154 | os.chmod(filepath, 0755) | ||
2198 | 2155 | Run(init[1]) | ||
2199 | 2156 | if os.path.isfile("/etc/waagent.conf"): | ||
2200 | 2157 | try: | ||
2201 | 2158 | os.remove("/etc/waagent.conf.old") | ||
2202 | 2159 | except: | ||
2203 | 2160 | pass | ||
2204 | 2161 | try: | ||
2205 | 2162 | os.rename("/etc/waagent.conf", "/etc/waagent.conf.old") | ||
2206 | 2163 | Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old") | ||
2207 | 2164 | except: | ||
2208 | 2165 | pass | ||
2209 | 2166 | SetFileContents("/etc/waagent.conf", WaagentConf) | ||
2210 | 2167 | SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate) | ||
2211 | 2168 | filepath = "/etc/ssh/sshd_config" | ||
2212 | 2169 | ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not | ||
2213 | 2170 | a.startswith("ClientAliveInterval"), | ||
2214 | 2171 | GetFileContents(filepath).split('\n'))) + "ClientAliveInterval 180\n") | ||
2215 | 2172 | Log("Configured SSH client probing to keep connections alive.") | ||
2216 | 2173 | ApplyVNUMAWorkaround() | ||
2217 | 2174 | return 0 | ||
2218 | 2175 | |||
2219 | 2176 | def Uninstall(): | ||
2220 | 2177 | if IsWindows(): | ||
2221 | 2178 | print("ERROR: -uninstall invalid for windows, see waagent_service.exe") | ||
2222 | 2179 | return 1 | ||
2223 | 2180 | SwitchCwd() | ||
2224 | 2181 | for a in RulesFiles: | ||
2225 | 2182 | if os.path.isfile(GetLastPathElement(a)): | ||
2226 | 2183 | try: | ||
2227 | 2184 | shutil.move(GetLastPathElement(a), a) | ||
2228 | 2185 | Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a ) | ||
2229 | 2186 | except: | ||
2230 | 2187 | pass | ||
2231 | 2188 | filename = "waagent" | ||
2232 | 2189 | a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3 | ||
2233 | 2190 | if a == 0: | ||
2234 | 2191 | Error("Unable to detect Linux Distribution.") | ||
2235 | 2192 | return 1 | ||
2236 | 2193 | Run("service " + filename + " stop") | ||
2237 | 2194 | cmd = ["chkconfig --del " + filename, | ||
2238 | 2195 | "update-rc.d -f " + filename + " remove", | ||
2239 | 2196 | "insserv -r " + filename][a - 1] | ||
2240 | 2197 | Run(cmd) | ||
2241 | 2198 | for f in os.listdir(LibDir) + ["/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]: | ||
2242 | 2199 | try: | ||
2243 | 2200 | os.remove(f) | ||
2244 | 2201 | except: | ||
2245 | 2202 | pass | ||
2246 | 2203 | RevertVNUMAWorkaround() | ||
2247 | 2204 | return 0 | ||
2248 | 2205 | |||
2249 | 2206 | def DeleteRootPassword(): | ||
2250 | 2207 | filepath="/etc/shadow" | ||
2251 | 2208 | ReplaceFileContentsAtomic(filepath, "root:*LOCK*:14600::::::\n" + "\n".join(filter(lambda a: not | ||
2252 | 2209 | a.startswith("root:"), | ||
2253 | 2210 | GetFileContents(filepath).split('\n')))) | ||
2254 | 2211 | os.chmod(filepath, 0000) | ||
2255 | 2212 | if IsRedHat(): | ||
2256 | 2213 | Run("chcon system_u:object_r:shadow_t:s0 " + filepath) | ||
2257 | 2214 | Log("Root password deleted.") | ||
2258 | 2215 | |||
2259 | 2216 | def Deprovision(force, deluser): | ||
2260 | 2217 | if IsWindows(): | ||
2261 | 2218 | Run(os.environ["windir"] + "\\system32\\sysprep\\sysprep.exe /generalize") | ||
2262 | 2219 | return 0 | ||
2263 | 2220 | |||
2264 | 2221 | SwitchCwd() | ||
2265 | 2222 | ovfxml = GetFileContents("ovf-env.xml") | ||
2266 | 2223 | ovfobj = None | ||
2267 | 2224 | if ovfxml != None: | ||
2268 | 2225 | ovfobj = OvfEnv().Parse(ovfxml) | ||
2269 | 2226 | |||
2270 | 2227 | print("WARNING! The waagent service will be stopped.") | ||
2271 | 2228 | print("WARNING! All SSH host key pairs will be deleted.") | ||
2272 | 2229 | print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.") | ||
2273 | 2230 | print("WARNING! Cached DHCP leases will be deleted.") | ||
2274 | 2231 | |||
2275 | 2232 | delRootPass = Config.get("Provisioning.DeleteRootPassword") | ||
2276 | 2233 | if delRootPass != None and delRootPass.lower().startswith("y"): | ||
2277 | 2234 | print("WARNING! root password will be disabled. You will not be able to login as root.") | ||
2278 | 2235 | |||
2279 | 2236 | if ovfobj != None and deluser == True: | ||
2280 | 2237 | print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.") | ||
2281 | 2238 | |||
2282 | 2239 | if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'): | ||
2283 | 2240 | return 1 | ||
2284 | 2241 | |||
2285 | 2242 | Run("service waagent stop") | ||
2286 | 2243 | |||
2287 | 2244 | if deluser == True: | ||
2288 | 2245 | DeleteAccount(ovfobj.UserName) | ||
2289 | 2246 | |||
2290 | 2247 | # Remove SSH host keys | ||
2291 | 2248 | regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair") | ||
2292 | 2249 | if regenerateKeys == None or regenerateKeys.lower().startswith("y"): | ||
2293 | 2250 | Run("rm -f /etc/ssh/ssh_host_*key*") | ||
2294 | 2251 | |||
2295 | 2252 | # Remove root password | ||
2296 | 2253 | if delRootPass != None and delRootPass.lower().startswith("y"): | ||
2297 | 2254 | DeleteRootPassword() | ||
2298 | 2255 | |||
2299 | 2256 | # Remove distribution specific networking configuration | ||
2300 | 2257 | |||
2301 | 2258 | UpdateAndPublishHostNameCommon("localhost.localdomain") | ||
2302 | 2259 | |||
2303 | 2260 | # RedHat, Suse, Debian | ||
2304 | 2261 | for a in VarLibDhcpDirectories: | ||
2305 | 2262 | Run("rm -f " + a + "/*") | ||
2306 | 2263 | |||
2307 | 2264 | # Clear LibDir, remove nameserver and root bash history | ||
2308 | 2265 | for f in os.listdir(LibDir) + ["/etc/resolv.conf", "/root/.bash_history", "/var/log/waagent.log"]: | ||
2309 | 2266 | try: | ||
2310 | 2267 | os.remove(f) | ||
2311 | 2268 | except: | ||
2312 | 2269 | pass | ||
2313 | 2270 | |||
2314 | 2271 | return 0 | ||
2315 | 2272 | |||
2316 | 2273 | def SwitchCwd(): | ||
2317 | 2274 | if not IsWindows(): | ||
2318 | 2275 | CreateDir(LibDir, "root", 0700) | ||
2319 | 2276 | os.chdir(LibDir) | ||
2320 | 2277 | |||
2321 | 2278 | def Usage(): | ||
2322 | 2279 | print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]") | ||
2323 | 2280 | return 0 | ||
2324 | 2281 | |||
2325 | 2282 | if GuestAgentVersion == "": | ||
2326 | 2283 | print("WARNING! This is a non-standard agent that does not include a valid version string.") | ||
2327 | 2284 | if IsLinux() and not DetectLinuxDistro(): | ||
2328 | 2285 | print("WARNING! Unable to detect Linux distribution. Some functionality may be broken.") | ||
2329 | 2286 | |||
2330 | 2287 | if len(sys.argv) == 1: | ||
2331 | 2288 | sys.exit(Usage()) | ||
2332 | 2289 | |||
2333 | 2290 | args = [] | ||
2334 | 2291 | force = False | ||
2335 | 2292 | for a in sys.argv[1:]: | ||
2336 | 2293 | if re.match("^([-/]*)(help|usage|\?)", a): | ||
2337 | 2294 | sys.exit(Usage()) | ||
2338 | 2295 | elif re.match("^([-/]*)verbose", a): | ||
2339 | 2296 | Verbose = True | ||
2340 | 2297 | elif re.match("^([-/]*)force", a): | ||
2341 | 2298 | force = True | ||
2342 | 2299 | elif re.match("^([-/]*)(setup|install)", a): | ||
2343 | 2300 | sys.exit(Install()) | ||
2344 | 2301 | elif re.match("^([-/]*)(uninstall)", a): | ||
2345 | 2302 | sys.exit(Uninstall()) | ||
2346 | 2303 | else: | ||
2347 | 2304 | args.append(a) | ||
2348 | 2305 | |||
2349 | 2306 | Config = ConfigurationProvider() | ||
2350 | 2307 | |||
2351 | 2308 | verbose = Config.get("Logs.Verbose") | ||
2352 | 2309 | if verbose != None and verbose.lower().startswith("y"): | ||
2353 | 2310 | Verbose = True | ||
2354 | 2311 | |||
2355 | 2312 | daemon = False | ||
2356 | 2313 | for a in args: | ||
2357 | 2314 | if re.match("^([-/]*)deprovision\+user", a): | ||
2358 | 2315 | sys.exit(Deprovision(force, True)) | ||
2359 | 2316 | elif re.match("^([-/]*)deprovision", a): | ||
2360 | 2317 | sys.exit(Deprovision(force, False)) | ||
2361 | 2318 | elif re.match("^([-/]*)daemon", a): | ||
2362 | 2319 | daemon = True | ||
2363 | 2320 | elif re.match("^([-/]*)version", a): | ||
2364 | 2321 | print(GuestAgentVersion + " running on " + LinuxDistro) | ||
2365 | 2322 | sys.exit(0) | ||
2366 | 2323 | elif re.match("^([-/]*)serialconsole", a): | ||
2367 | 2324 | AddToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0") | ||
2368 | 2325 | Log("Configured kernel to use ttyS0 as the boot console.") | ||
2369 | 2326 | sys.exit(0) | ||
2370 | 2327 | else: | ||
2371 | 2328 | print("Invalid command line parameter:" + a) | ||
2372 | 2329 | sys.exit(1) | ||
2373 | 2330 | |||
2374 | 2331 | if daemon == False: | ||
2375 | 2332 | sys.exit(Usage()) | ||
2376 | 2333 | |||
2377 | 2334 | try: | ||
2378 | 2335 | SwitchCwd() | ||
2379 | 2336 | Log(GuestAgentLongName + " Version: " + GuestAgentVersion) | ||
2380 | 2337 | if IsLinux(): | ||
2381 | 2338 | Log("Linux Distribution Detected : " + LinuxDistro) | ||
2382 | 2339 | WaAgent = Agent() | ||
2383 | 2340 | WaAgent.Run() | ||
2384 | 2341 | except Exception, e: | ||
2385 | 2342 | Error(traceback.format_exc()) | ||
2386 | 2343 | Error("Exception: " + str(e)) | ||
2387 | 2344 | sys.exit(1) | ||
2388 | 2345 | 0 | ||
2389 | === added directory '.pc/000_use_package_upstart.patch' | |||
2390 | === added file '.pc/000_use_package_upstart.patch/waagent' | |||
2391 | --- .pc/000_use_package_upstart.patch/waagent 1970-01-01 00:00:00 +0000 | |||
2392 | +++ .pc/000_use_package_upstart.patch/waagent 2012-12-13 16:36:21 +0000 | |||
2393 | @@ -0,0 +1,2473 @@ | |||
2394 | 1 | #!/usr/bin/python | ||
2395 | 2 | # | ||
2396 | 3 | # Windows Azure Linux Agent | ||
2397 | 4 | # | ||
2398 | 5 | # Copyright 2012 Microsoft Corporation | ||
2399 | 6 | # | ||
2400 | 7 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
2401 | 8 | # you may not use this file except in compliance with the License. | ||
2402 | 9 | # You may obtain a copy of the License at | ||
2403 | 10 | # | ||
2404 | 11 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
2405 | 12 | # | ||
2406 | 13 | # Unless required by applicable law or agreed to in writing, software | ||
2407 | 14 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
2408 | 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
2409 | 16 | # See the License for the specific language governing permissions and | ||
2410 | 17 | # limitations under the License. | ||
2411 | 18 | # | ||
2412 | 19 | # Requires Python 2.4+ and Openssl 1.0+ | ||
2413 | 20 | # | ||
2414 | 21 | # Implements parts of RFC 2131, 1541, 1497 and | ||
2415 | 22 | # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx | ||
2416 | 23 | # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx | ||
2417 | 24 | # | ||
2418 | 25 | |||
2419 | 26 | import array | ||
2420 | 27 | import base64 | ||
2421 | 28 | import httplib | ||
2422 | 29 | import os | ||
2423 | 30 | import os.path | ||
2424 | 31 | import platform | ||
2425 | 32 | import pwd | ||
2426 | 33 | import re | ||
2427 | 34 | import shutil | ||
2428 | 35 | import socket | ||
2429 | 36 | import SocketServer | ||
2430 | 37 | import struct | ||
2431 | 38 | import subprocess | ||
2432 | 39 | import sys | ||
2433 | 40 | import tempfile | ||
2434 | 41 | import textwrap | ||
2435 | 42 | import threading | ||
2436 | 43 | import time | ||
2437 | 44 | import traceback | ||
2438 | 45 | import xml.dom.minidom | ||
2439 | 46 | import commands | ||
2440 | 47 | |||
2441 | 48 | GuestAgentName = "WALinuxAgent" | ||
2442 | 49 | GuestAgentLongName = "Windows Azure Linux Agent" | ||
2443 | 50 | GuestAgentVersion = "WALinuxAgent-1.2" | ||
2444 | 51 | ProtocolVersion = "2011-12-31" | ||
2445 | 52 | |||
2446 | 53 | Config = None | ||
2447 | 54 | LinuxDistro = "UNKNOWN" | ||
2448 | 55 | Verbose = False | ||
2449 | 56 | WaAgent = None | ||
2450 | 57 | DiskActivated = False | ||
2451 | 58 | Openssl = "openssl" | ||
2452 | 59 | Children = [] | ||
2453 | 60 | |||
2454 | 61 | PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"] | ||
2455 | 62 | RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules", | ||
2456 | 63 | "/etc/udev/rules.d/70-persistent-net.rules" ] | ||
2457 | 64 | VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"] | ||
2458 | 65 | EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"] | ||
2459 | 66 | LibDir = "/var/lib/waagent" | ||
2460 | 67 | |||
2461 | 68 | # This lets us index into a string or an array of integers transparently. | ||
2462 | 69 | def Ord(a): | ||
2463 | 70 | if type(a) == type("a"): | ||
2464 | 71 | a = ord(a) | ||
2465 | 72 | return a | ||
2466 | 73 | |||
2467 | 74 | def IsWindows(): | ||
2468 | 75 | return (platform.uname()[0] == "Windows") | ||
2469 | 76 | |||
2470 | 77 | def IsLinux(): | ||
2471 | 78 | return (platform.uname()[0] == "Linux") | ||
2472 | 79 | |||
2473 | 80 | def DetectLinuxDistro(): | ||
2474 | 81 | global LinuxDistro | ||
2475 | 82 | if os.path.isfile("/etc/redhat-release"): | ||
2476 | 83 | LinuxDistro = "RedHat" | ||
2477 | 84 | return True | ||
2478 | 85 | if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"): | ||
2479 | 86 | LinuxDistro = "Ubuntu" | ||
2480 | 87 | return True | ||
2481 | 88 | if os.path.isfile("/etc/debian_version"): | ||
2482 | 89 | LinuxDistro = "Debian" | ||
2483 | 90 | return True | ||
2484 | 91 | if os.path.isfile("/etc/SuSE-release"): | ||
2485 | 92 | LinuxDistro = "Suse" | ||
2486 | 93 | return True | ||
2487 | 94 | return False | ||
2488 | 95 | |||
2489 | 96 | def IsRedHat(): | ||
2490 | 97 | return "RedHat" in LinuxDistro | ||
2491 | 98 | |||
2492 | 99 | def IsUbuntu(): | ||
2493 | 100 | return "Ubuntu" in LinuxDistro | ||
2494 | 101 | |||
2495 | 102 | def IsDebian(): | ||
2496 | 103 | return IsUbuntu() or "Debian" in LinuxDistro | ||
2497 | 104 | |||
2498 | 105 | def IsSuse(): | ||
2499 | 106 | return "Suse" in LinuxDistro | ||
2500 | 107 | |||
2501 | 108 | def UsesRpm(): | ||
2502 | 109 | return IsRedHat() or IsSuse() | ||
2503 | 110 | |||
2504 | 111 | def UsesDpkg(): | ||
2505 | 112 | return IsDebian() | ||
2506 | 113 | |||
2507 | 114 | def GetLastPathElement(path): | ||
2508 | 115 | return path.rsplit('/', 1)[1] | ||
2509 | 116 | |||
2510 | 117 | def GetFileContents(filepath): | ||
2511 | 118 | file = None | ||
2512 | 119 | try: | ||
2513 | 120 | file = open(filepath) | ||
2514 | 121 | except: | ||
2515 | 122 | return None | ||
2516 | 123 | if file == None: | ||
2517 | 124 | return None | ||
2518 | 125 | try: | ||
2519 | 126 | return file.read() | ||
2520 | 127 | finally: | ||
2521 | 128 | file.close() | ||
2522 | 129 | |||
2523 | 130 | def SetFileContents(filepath, contents): | ||
2524 | 131 | file = open(filepath, "w") | ||
2525 | 132 | try: | ||
2526 | 133 | file.write(contents) | ||
2527 | 134 | finally: | ||
2528 | 135 | file.close() | ||
2529 | 136 | |||
2530 | 137 | def AppendFileContents(filepath, contents): | ||
2531 | 138 | file = open(filepath, "a") | ||
2532 | 139 | try: | ||
2533 | 140 | file.write(contents) | ||
2534 | 141 | finally: | ||
2535 | 142 | file.close() | ||
2536 | 143 | |||
2537 | 144 | def ReplaceFileContentsAtomic(filepath, contents): | ||
2538 | 145 | handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath)) | ||
2539 | 146 | try: | ||
2540 | 147 | os.write(handle, contents) | ||
2541 | 148 | finally: | ||
2542 | 149 | os.close(handle) | ||
2543 | 150 | try: | ||
2544 | 151 | os.rename(temp, filepath) | ||
2545 | 152 | return | ||
2546 | 153 | except: | ||
2547 | 154 | pass | ||
2548 | 155 | os.remove(filepath) | ||
2549 | 156 | os.rename(temp, filepath) | ||
2550 | 157 | |||
2551 | 158 | def GetLineStartingWith(prefix, filepath): | ||
2552 | 159 | for line in GetFileContents(filepath).split('\n'): | ||
2553 | 160 | if line.startswith(prefix): | ||
2554 | 161 | return line | ||
2555 | 162 | return None | ||
2556 | 163 | |||
2557 | 164 | def Run(a): | ||
2558 | 165 | LogIfVerbose(a) | ||
2559 | 166 | return os.system(a) | ||
2560 | 167 | |||
2561 | 168 | def RunSafe(cmd): | ||
2562 | 169 | LogIfVerbose(cmd) | ||
2563 | 170 | # for python2.1 double try, in order to use a finally... | ||
2564 | 171 | try: | ||
2565 | 172 | try: | ||
2566 | 173 | (exit_status,output) = commands.getstatusoutput(cmd) | ||
2567 | 174 | except OSError,e : # just catch the exception and proceed | ||
2568 | 175 | LogIfVerbose( ("OSError " + str(e) + " caught") ) | ||
2569 | 176 | return exit_status,output | ||
2570 | 177 | else: | ||
2571 | 178 | return exit_status,output | ||
2572 | 179 | finally: | ||
2573 | 180 | pass | ||
2574 | 181 | |||
2575 | 182 | def GetNodeTextData(a): | ||
2576 | 183 | for b in a.childNodes: | ||
2577 | 184 | if b.nodeType == b.TEXT_NODE: | ||
2578 | 185 | return b.data | ||
2579 | 186 | |||
2580 | 187 | def GetHome(): | ||
2581 | 188 | home = None | ||
2582 | 189 | try: | ||
2583 | 190 | home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip() | ||
2584 | 191 | except: | ||
2585 | 192 | pass | ||
2586 | 193 | if (home == None) or (home.startswith("/") == False): | ||
2587 | 194 | home = "/home" | ||
2588 | 195 | return home | ||
2589 | 196 | |||
2590 | 197 | def ChangeOwner(filepath, user): | ||
2591 | 198 | p = None | ||
2592 | 199 | try: | ||
2593 | 200 | p = pwd.getpwnam(user) | ||
2594 | 201 | except: | ||
2595 | 202 | pass | ||
2596 | 203 | if p != None: | ||
2597 | 204 | os.chown(filepath, p[2], p[3]) | ||
2598 | 205 | |||
2599 | 206 | def CreateDir(dirpath, user, mode): | ||
2600 | 207 | try: | ||
2601 | 208 | os.makedirs(dirpath, mode) | ||
2602 | 209 | except: | ||
2603 | 210 | pass | ||
2604 | 211 | ChangeOwner(dirpath, user) | ||
2605 | 212 | |||
2606 | 213 | def CreateAccount(user, password, expiration, thumbprint): | ||
2607 | 214 | if IsWindows(): | ||
2608 | 215 | Log("Skipping CreateAccount on Windows") | ||
2609 | 216 | return None | ||
2610 | 217 | userentry = None | ||
2611 | 218 | try: | ||
2612 | 219 | userentry = pwd.getpwnam(user) | ||
2613 | 220 | except: | ||
2614 | 221 | pass | ||
2615 | 222 | uidmin = None | ||
2616 | 223 | try: | ||
2617 | 224 | uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1]) | ||
2618 | 225 | except: | ||
2619 | 226 | pass | ||
2620 | 227 | if uidmin == None: | ||
2621 | 228 | uidmin = 100 | ||
2622 | 229 | if userentry != None and userentry[2] < uidmin: | ||
2623 | 230 | Error("CreateAccount: " + user + " is a system user. Will not set password.") | ||
2624 | 231 | return "Failed to set password for system user: " + user + " (0x06)." | ||
2625 | 232 | if userentry == None: | ||
2626 | 233 | command = "useradd -m " + user | ||
2627 | 234 | if expiration != None: | ||
2628 | 235 | command += " -e " + expiration.split('.')[0] | ||
2629 | 236 | if Run(command): | ||
2630 | 237 | Error("Failed to create user account: " + user) | ||
2631 | 238 | return "Failed to create user account: " + user + " (0x07)." | ||
2632 | 239 | else: | ||
2633 | 240 | Log("CreateAccount: " + user + " already exists. Will update password.") | ||
2634 | 241 | if password != None: | ||
2635 | 242 | os.popen("chpasswd", "w").write(user + ":" + password + "\n") | ||
2636 | 243 | try: | ||
2637 | 244 | if password == None: | ||
2638 | 245 | SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n") | ||
2639 | 246 | else: | ||
2640 | 247 | SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n") | ||
2641 | 248 | os.chmod("/etc/sudoers.d/waagent", 0440) | ||
2642 | 249 | except: | ||
2643 | 250 | Error("CreateAccount: Failed to configure sudo access for user.") | ||
2644 | 251 | return "Failed to configure sudo privileges (0x08)." | ||
2645 | 252 | home = GetHome() | ||
2646 | 253 | if thumbprint != None: | ||
2647 | 254 | dir = home + "/" + user + "/.ssh" | ||
2648 | 255 | CreateDir(dir, user, 0700) | ||
2649 | 256 | pub = dir + "/id_rsa.pub" | ||
2650 | 257 | prv = dir + "/id_rsa" | ||
2651 | 258 | Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub) | ||
2652 | 259 | SetFileContents(prv, GetFileContents(thumbprint + ".prv")) | ||
2653 | 260 | for f in [pub, prv]: | ||
2654 | 261 | os.chmod(f, 0600) | ||
2655 | 262 | ChangeOwner(f, user) | ||
2656 | 263 | SetFileContents(dir + "/authorized_keys", GetFileContents(pub)) | ||
2657 | 264 | ChangeOwner(dir + "/authorized_keys", user) | ||
2658 | 265 | Log("Created user account: " + user) | ||
2659 | 266 | return None | ||
2660 | 267 | |||
2661 | 268 | def DeleteAccount(user): | ||
2662 | 269 | if IsWindows(): | ||
2663 | 270 | Log("Skipping DeleteAccount on Windows") | ||
2664 | 271 | return | ||
2665 | 272 | userentry = None | ||
2666 | 273 | try: | ||
2667 | 274 | userentry = pwd.getpwnam(user) | ||
2668 | 275 | except: | ||
2669 | 276 | pass | ||
2670 | 277 | if userentry == None: | ||
2671 | 278 | Error("DeleteAccount: " + user + " not found.") | ||
2672 | 279 | return | ||
2673 | 280 | uidmin = None | ||
2674 | 281 | try: | ||
2675 | 282 | uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1]) | ||
2676 | 283 | except: | ||
2677 | 284 | pass | ||
2678 | 285 | if uidmin == None: | ||
2679 | 286 | uidmin = 100 | ||
2680 | 287 | if userentry[2] < uidmin: | ||
2681 | 288 | Error("DeleteAccount: " + user + " is a system user. Will not delete account.") | ||
2682 | 289 | return | ||
2683 | 290 | Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted | ||
2684 | 291 | Run("userdel -f -r " + user) | ||
2685 | 292 | try: | ||
2686 | 293 | os.remove("/etc/sudoers.d/waagent") | ||
2687 | 294 | except: | ||
2688 | 295 | pass | ||
2689 | 296 | return | ||
2690 | 297 | |||
2691 | 298 | def ReloadSshd(): | ||
2692 | 299 | name = None | ||
2693 | 300 | if IsRedHat() or IsSuse(): | ||
2694 | 301 | name = "sshd" | ||
2695 | 302 | if IsDebian(): | ||
2696 | 303 | name = "ssh" | ||
2697 | 304 | if name == None: | ||
2698 | 305 | return | ||
2699 | 306 | if not Run("service " + name + " status | grep running"): | ||
2700 | 307 | Run("service " + name + " reload") | ||
2701 | 308 | |||
2702 | 309 | def IsInRangeInclusive(a, low, high): | ||
2703 | 310 | return (a >= low and a <= high) | ||
2704 | 311 | |||
2705 | 312 | def IsPrintable(ch): | ||
2706 | 313 | return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9')) | ||
2707 | 314 | |||
2708 | 315 | def HexDump(buffer, size): | ||
2709 | 316 | if size < 0: | ||
2710 | 317 | size = len(buffer) | ||
2711 | 318 | result = "" | ||
2712 | 319 | for i in range(0, size): | ||
2713 | 320 | if (i % 16) == 0: | ||
2714 | 321 | result += "%06X: " % i | ||
2715 | 322 | byte = struct.unpack("B", buffer[i])[0] | ||
2716 | 323 | result += "%02X " % byte | ||
2717 | 324 | if (i & 15) == 7: | ||
2718 | 325 | result += " " | ||
2719 | 326 | if ((i + 1) % 16) == 0 or (i + 1) == size: | ||
2720 | 327 | j = i | ||
2721 | 328 | while ((j + 1) % 16) != 0: | ||
2722 | 329 | result += " " | ||
2723 | 330 | if (j & 7) == 7: | ||
2724 | 331 | result += " " | ||
2725 | 332 | j += 1 | ||
2726 | 333 | result += " " | ||
2727 | 334 | for j in range(i - (i % 16), i + 1): | ||
2728 | 335 | byte = struct.unpack("B", buffer[j])[0] | ||
2729 | 336 | k = '.' | ||
2730 | 337 | if IsPrintable(byte): | ||
2731 | 338 | k = chr(byte) | ||
2732 | 339 | result += k | ||
2733 | 340 | if (i + 1) != size: | ||
2734 | 341 | result += "\n" | ||
2735 | 342 | return result | ||
2736 | 343 | |||
2737 | 344 | def ThrottleLog(counter): | ||
2738 | 345 | # Log everything up to 10, every 10 up to 100, then every 100. | ||
2739 | 346 | return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0) | ||
2740 | 347 | |||
2741 | 348 | def Logger(): | ||
2742 | 349 | class T(object): | ||
2743 | 350 | def __init__(self): | ||
2744 | 351 | self.File = None | ||
2745 | 352 | |||
2746 | 353 | self = T() | ||
2747 | 354 | |||
2748 | 355 | def LogToFile(message): | ||
2749 | 356 | FilePath = ["/var/log/waagent.log", "waagent.log"][IsWindows()] | ||
2750 | 357 | if not os.path.isfile(FilePath) and self.File != None: | ||
2751 | 358 | self.File.close() | ||
2752 | 359 | self.File = None | ||
2753 | 360 | if self.File == None: | ||
2754 | 361 | self.File = open(FilePath, "a") | ||
2755 | 362 | self.File.write(message + "\n") | ||
2756 | 363 | self.File.flush() | ||
2757 | 364 | |||
2758 | 365 | def Log(message): | ||
2759 | 366 | LogWithPrefix("", message) | ||
2760 | 367 | |||
2761 | 368 | def LogWithPrefix(prefix, message): | ||
2762 | 369 | t = time.localtime() | ||
2763 | 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) | ||
2764 | 371 | t += prefix | ||
2765 | 372 | for line in message.split('\n'): | ||
2766 | 373 | line = t + line | ||
2767 | 374 | print(line) | ||
2768 | 375 | LogToFile(line) | ||
2769 | 376 | |||
2770 | 377 | return Log, LogWithPrefix | ||
2771 | 378 | |||
2772 | 379 | Log, LogWithPrefix = Logger() | ||
2773 | 380 | |||
2774 | 381 | def NoLog(message): | ||
2775 | 382 | pass | ||
2776 | 383 | |||
2777 | 384 | def LogIfVerbose(message): | ||
2778 | 385 | if Verbose == True: | ||
2779 | 386 | Log(message) | ||
2780 | 387 | |||
2781 | 388 | def LogWithPrefixIfVerbose(prefix, message): | ||
2782 | 389 | if Verbose == True: | ||
2783 | 390 | LogWithPrefix(prefix, message) | ||
2784 | 391 | |||
2785 | 392 | def Warn(message): | ||
2786 | 393 | LogWithPrefix("WARNING:", message) | ||
2787 | 394 | |||
2788 | 395 | def Error(message): | ||
2789 | 396 | LogWithPrefix("ERROR:", message) | ||
2790 | 397 | |||
2791 | 398 | def ErrorWithPrefix(prefix, message): | ||
2792 | 399 | LogWithPrefix("ERROR:" + prefix, message) | ||
2793 | 400 | |||
2794 | 401 | def Linux_ioctl_GetIpv4Address(ifname): | ||
2795 | 402 | import fcntl | ||
2796 | 403 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
2797 | 404 | return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24]) | ||
2798 | 405 | |||
2799 | 406 | def Linux_ioctl_GetInterfaceMac(ifname): | ||
2800 | 407 | import fcntl | ||
2801 | 408 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
2802 | 409 | info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15])) | ||
2803 | 410 | return ''.join(['%02X' % Ord(char) for char in info[18:24]]) | ||
2804 | 411 | |||
2805 | 412 | def GetIpv4Address(): | ||
2806 | 413 | if IsLinux(): | ||
2807 | 414 | for ifname in PossibleEthernetInterfaces: | ||
2808 | 415 | try: | ||
2809 | 416 | return Linux_ioctl_GetIpv4Address(ifname) | ||
2810 | 417 | except IOError, e: | ||
2811 | 418 | pass | ||
2812 | 419 | else: | ||
2813 | 420 | try: | ||
2814 | 421 | return socket.gethostbyname(socket.gethostname()) | ||
2815 | 422 | except Exception, e: | ||
2816 | 423 | ErrorWithPrefix("GetIpv4Address:", str(e)) | ||
2817 | 424 | ErrorWithPrefix("GetIpv4Address:", traceback.format_exc()) | ||
2818 | 425 | |||
2819 | 426 | def HexStringToByteArray(a): | ||
2820 | 427 | b = "" | ||
2821 | 428 | for c in range(0, len(a) / 2): | ||
2822 | 429 | b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16)) | ||
2823 | 430 | return b | ||
2824 | 431 | |||
2825 | 432 | def GetMacAddress(): | ||
2826 | 433 | if IsWindows(): | ||
2827 | 434 | # Windows: Physical Address. . . . . . . . . : 00-15-17-79-00-7F\n | ||
2828 | 435 | a = "ipconfig /all | findstr /c:\"Physical Address\" | findstr /v \"00-00-00-00-00-00-00\"" | ||
2829 | 436 | a = os.popen(a).read() | ||
2830 | 437 | a = re.sub("\s+$", "", a) | ||
2831 | 438 | a = re.sub(".+ ", "", a) | ||
2832 | 439 | a = re.sub(":", "", a) | ||
2833 | 440 | a = re.sub("-", "", a) | ||
2834 | 441 | else: | ||
2835 | 442 | for ifname in PossibleEthernetInterfaces: | ||
2836 | 443 | try: | ||
2837 | 444 | a = Linux_ioctl_GetInterfaceMac(ifname) | ||
2838 | 445 | break | ||
2839 | 446 | except IOError, e: | ||
2840 | 447 | pass | ||
2841 | 448 | return HexStringToByteArray(a) | ||
2842 | 449 | |||
2843 | 450 | def DeviceForIdePort(n): | ||
2844 | 451 | if n > 3: | ||
2845 | 452 | return None | ||
2846 | 453 | g0 = "00000000" | ||
2847 | 454 | if n > 1: | ||
2848 | 455 | g0 = "00000001" | ||
2849 | 456 | n = n - 2 | ||
2850 | 457 | device = None | ||
2851 | 458 | path = "/sys/bus/vmbus/devices/" | ||
2852 | 459 | for vmbus in os.listdir(path): | ||
2853 | 460 | guid = GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-') | ||
2854 | 461 | if guid[0] == g0 and guid[1] == "000" + str(n): | ||
2855 | 462 | for root, dirs, files in os.walk(path + vmbus): | ||
2856 | 463 | if root.endswith("/block"): | ||
2857 | 464 | device = dirs[0] | ||
2858 | 465 | break | ||
2859 | 466 | break | ||
2860 | 467 | return device | ||
2861 | 468 | |||
2862 | 469 | class Util(object): | ||
2863 | 470 | def _HttpGet(self, url, headers): | ||
2864 | 471 | LogIfVerbose("HttpGet(" + url + ")") | ||
2865 | 472 | maxRetry = 2 | ||
2866 | 473 | if url.startswith("http://"): | ||
2867 | 474 | url = url[7:] | ||
2868 | 475 | url = url[url.index("/"):] | ||
2869 | 476 | for retry in range(0, maxRetry + 1): | ||
2870 | 477 | strRetry = str(retry) | ||
2871 | 478 | log = [NoLog, Log][retry > 0] | ||
2872 | 479 | log("retry HttpGet(" + url + "),retry=" + strRetry) | ||
2873 | 480 | response = None | ||
2874 | 481 | strStatus = "None" | ||
2875 | 482 | try: | ||
2876 | 483 | httpConnection = httplib.HTTPConnection(self.Endpoint) | ||
2877 | 484 | if headers == None: | ||
2878 | 485 | request = httpConnection.request("GET", url) | ||
2879 | 486 | else: | ||
2880 | 487 | request = httpConnection.request("GET", url, None, headers) | ||
2881 | 488 | response = httpConnection.getresponse() | ||
2882 | 489 | strStatus = str(response.status) | ||
2883 | 490 | except: | ||
2884 | 491 | pass | ||
2885 | 492 | log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2886 | 493 | if response == None or response.status != httplib.OK: | ||
2887 | 494 | Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2888 | 495 | if retry == maxRetry: | ||
2889 | 496 | Log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2890 | 497 | return None | ||
2891 | 498 | else: | ||
2892 | 499 | Log("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2893 | 500 | time.sleep(10) | ||
2894 | 501 | else: | ||
2895 | 502 | log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2896 | 503 | return response.read() | ||
2897 | 504 | |||
2898 | 505 | def HttpGetWithoutHeaders(self, url): | ||
2899 | 506 | return self._HttpGet(url, None) | ||
2900 | 507 | |||
2901 | 508 | def HttpGetWithHeaders(self, url): | ||
2902 | 509 | return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion}) | ||
2903 | 510 | |||
2904 | 511 | def HttpSecureGetWithHeaders(self, url, transportCert): | ||
2905 | 512 | return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, | ||
2906 | 513 | "x-ms-version": ProtocolVersion, | ||
2907 | 514 | "x-ms-cipher-name": "DES_EDE3_CBC", | ||
2908 | 515 | "x-ms-guest-agent-public-x509-cert": transportCert}) | ||
2909 | 516 | |||
2910 | 517 | def HttpPost(self, url, data): | ||
2911 | 518 | LogIfVerbose("HttpPost(" + url + ")") | ||
2912 | 519 | maxRetry = 2 | ||
2913 | 520 | for retry in range(0, maxRetry + 1): | ||
2914 | 521 | strRetry = str(retry) | ||
2915 | 522 | log = [NoLog, Log][retry > 0] | ||
2916 | 523 | log("retry HttpPost(" + url + "),retry=" + strRetry) | ||
2917 | 524 | response = None | ||
2918 | 525 | strStatus = "None" | ||
2919 | 526 | try: | ||
2920 | 527 | httpConnection = httplib.HTTPConnection(self.Endpoint) | ||
2921 | 528 | request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName, | ||
2922 | 529 | "Content-Type": "text/xml; charset=utf-8", | ||
2923 | 530 | "x-ms-version": ProtocolVersion}) | ||
2924 | 531 | response = httpConnection.getresponse() | ||
2925 | 532 | strStatus = str(response.status) | ||
2926 | 533 | except: | ||
2927 | 534 | pass | ||
2928 | 535 | log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2929 | 536 | if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED): | ||
2930 | 537 | Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2931 | 538 | if retry == maxRetry: | ||
2932 | 539 | Log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2933 | 540 | return None | ||
2934 | 541 | else: | ||
2935 | 542 | Log("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2936 | 543 | time.sleep(10) | ||
2937 | 544 | else: | ||
2938 | 545 | log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) | ||
2939 | 546 | return response | ||
2940 | 547 | |||
2941 | 548 | def LoadBalancerProbeServer(port): | ||
2942 | 549 | |||
2943 | 550 | class T(object): | ||
2944 | 551 | def __init__(self, ip, port): | ||
2945 | 552 | if port == None or ip == None : | ||
2946 | 553 | return | ||
2947 | 554 | self.ProbeCounter = 0 | ||
2948 | 555 | self.server = SocketServer.TCPServer((ip, port), TCPHandler) | ||
2949 | 556 | self.server_thread = threading.Thread(target = self.server.serve_forever) | ||
2950 | 557 | self.server_thread.setDaemon(True) | ||
2951 | 558 | self.server_thread.start() | ||
2952 | 559 | |||
2953 | 560 | def shutdown(self): | ||
2954 | 561 | self.server.shutdown() | ||
2955 | 562 | |||
2956 | 563 | class TCPHandler(SocketServer.BaseRequestHandler): | ||
2957 | 564 | def GetHttpDateTimeNow(self): | ||
2958 | 565 | # Date: Fri, 25 Mar 2011 04:53:10 GMT | ||
2959 | 566 | return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) | ||
2960 | 567 | |||
2961 | 568 | def handle(self): | ||
2962 | 569 | context.ProbeCounter = (context.ProbeCounter + 1) % 1000000 | ||
2963 | 570 | log = [NoLog, LogIfVerbose][ThrottleLog(context.ProbeCounter)] | ||
2964 | 571 | strCounter = str(context.ProbeCounter) | ||
2965 | 572 | if context.ProbeCounter == 1: | ||
2966 | 573 | Log("Receiving LB probes.") | ||
2967 | 574 | log("Received LB probe # " + strCounter) | ||
2968 | 575 | self.request.recv(1024) | ||
2969 | 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") | ||
2970 | 577 | |||
2971 | 578 | for retry in range(1,6): | ||
2972 | 579 | context=None | ||
2973 | 580 | ip = GetIpv4Address() | ||
2974 | 581 | if ip == None : | ||
2975 | 582 | Log("LoadBalancerProbeServer: GetIpv4Address() returned None, sleeping 10 before retry " + str(retry+1) ) | ||
2976 | 583 | time.sleep(10) | ||
2977 | 584 | else: | ||
2978 | 585 | try: | ||
2979 | 586 | context = T(ip,port) | ||
2980 | 587 | break | ||
2981 | 588 | except Exception, e: | ||
2982 | 589 | Log("LoadBalancerProbeServer: Exception contructing socket server: " + str(e)) | ||
2983 | 590 | Log("LoadBalancerProbeServer: Retry socket server construction #" + str(retry+1) ) | ||
2984 | 591 | return context | ||
2985 | 592 | |||
2986 | 593 | class ConfigurationProvider(object): | ||
2987 | 594 | def __init__(self): | ||
2988 | 595 | self.values = dict() | ||
2989 | 596 | if os.path.isfile("/etc/waagent.conf") == False: | ||
2990 | 597 | raise Exception("Missing configuration in /etc/waagent.conf") | ||
2991 | 598 | try: | ||
2992 | 599 | for line in GetFileContents("/etc/waagent.conf").split('\n'): | ||
2993 | 600 | if not line.startswith("#") and "=" in line: | ||
2994 | 601 | parts = line.split()[0].split('=') | ||
2995 | 602 | value = parts[1].strip("\" ") | ||
2996 | 603 | if value != "None": | ||
2997 | 604 | self.values[parts[0]] = value | ||
2998 | 605 | else: | ||
2999 | 606 | self.values[parts[0]] = None | ||
3000 | 607 | except: | ||
3001 | 608 | Error("Unable to parse /etc/waagent.conf") | ||
3002 | 609 | raise | ||
3003 | 610 | return | ||
3004 | 611 | |||
3005 | 612 | def get(self, key): | ||
3006 | 613 | return self.values.get(key) | ||
3007 | 614 | |||
3008 | 615 | class EnvMonitor(object): | ||
3009 | 616 | def __init__(self): | ||
3010 | 617 | self.shutdown = False | ||
3011 | 618 | self.HostName = socket.gethostname() | ||
3012 | 619 | self.server_thread = threading.Thread(target = self.monitor) | ||
3013 | 620 | self.server_thread.setDaemon(True) | ||
3014 | 621 | self.server_thread.start() | ||
3015 | 622 | self.published = False | ||
3016 | 623 | |||
3017 | 624 | def monitor(self): | ||
3018 | 625 | publish = Config.get("Provisioning.MonitorHostName") | ||
3019 | 626 | dhcpcmd = "pidof dhclient" | ||
3020 | 627 | if IsSuse(): | ||
3021 | 628 | dhcpcmd = "pidof dhcpcd" | ||
3022 | 629 | if IsDebian(): | ||
3023 | 630 | dhcpcmd = "pidof dhclient3" | ||
3024 | 631 | dhcppid = os.popen(dhcpcmd).read() | ||
3025 | 632 | while not self.shutdown: | ||
3026 | 633 | for a in RulesFiles: | ||
3027 | 634 | if os.path.isfile(a): | ||
3028 | 635 | if os.path.isfile(GetLastPathElement(a)): | ||
3029 | 636 | os.remove(GetLastPathElement(a)) | ||
3030 | 637 | shutil.move(a, ".") | ||
3031 | 638 | Log("EnvMonitor: Moved " + a + " -> " + LibDir) | ||
3032 | 639 | if publish != None and publish.lower().startswith("y"): | ||
3033 | 640 | try: | ||
3034 | 641 | if socket.gethostname() != self.HostName: | ||
3035 | 642 | Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname()) | ||
3036 | 643 | self.HostName = socket.gethostname() | ||
3037 | 644 | WaAgent.UpdateAndPublishHostName(self.HostName) | ||
3038 | 645 | dhcppid = os.popen(dhcpcmd).read() | ||
3039 | 646 | self.published = True | ||
3040 | 647 | except: | ||
3041 | 648 | pass | ||
3042 | 649 | else: | ||
3043 | 650 | self.published = True | ||
3044 | 651 | pid = "" | ||
3045 | 652 | if not os.path.isdir("/proc/" + dhcppid.strip()): | ||
3046 | 653 | pid = os.popen(dhcpcmd).read() | ||
3047 | 654 | if pid != "" and pid != dhcppid: | ||
3048 | 655 | Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.") | ||
3049 | 656 | WaAgent.RestoreRoutes() | ||
3050 | 657 | dhcppid = pid | ||
3051 | 658 | for child in Children: | ||
3052 | 659 | if child.poll() != None: | ||
3053 | 660 | Children.remove(child) | ||
3054 | 661 | time.sleep(5) | ||
3055 | 662 | |||
3056 | 663 | def SetHostName(self, name): | ||
3057 | 664 | if socket.gethostname() == name: | ||
3058 | 665 | self.published = True | ||
3059 | 666 | elif Run("hostname " + name): | ||
3060 | 667 | Error("Error: SetHostName: Cannot set hostname to " + name) | ||
3061 | 668 | return ("Error: SetHostName: Cannot set hostname to " + name) | ||
3062 | 669 | |||
3063 | 670 | def IsNamePublished(self): | ||
3064 | 671 | return self.published | ||
3065 | 672 | |||
3066 | 673 | def ShutdownService(self): | ||
3067 | 674 | self.shutdown = True | ||
3068 | 675 | self.server_thread.join() | ||
3069 | 676 | |||
3070 | 677 | class Certificates(object): | ||
3071 | 678 | # | ||
3072 | 679 | # <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd"> | ||
3073 | 680 | # <Version>2010-12-15</Version> | ||
3074 | 681 | # <Incarnation>2</Incarnation> | ||
3075 | 682 | # <Format>Pkcs7BlobWithPfxContents</Format> | ||
3076 | 683 | # <Data>MIILTAY... | ||
3077 | 684 | # </Data> | ||
3078 | 685 | # </CertificateFile> | ||
3079 | 686 | # | ||
3080 | 687 | def __init__(self): | ||
3081 | 688 | self.reinitialize() | ||
3082 | 689 | |||
3083 | 690 | def reinitialize(self): | ||
3084 | 691 | self.Incarnation = None | ||
3085 | 692 | self.Role = None | ||
3086 | 693 | |||
3087 | 694 | def Parse(self, xmlText): | ||
3088 | 695 | self.reinitialize() | ||
3089 | 696 | SetFileContents("Certificates.xml", xmlText) | ||
3090 | 697 | dom = xml.dom.minidom.parseString(xmlText) | ||
3091 | 698 | for a in [ "CertificateFile", "Version", "Incarnation", | ||
3092 | 699 | "Format", "Data", ]: | ||
3093 | 700 | if not dom.getElementsByTagName(a): | ||
3094 | 701 | Error("Certificates.Parse: Missing " + a) | ||
3095 | 702 | return None | ||
3096 | 703 | node = dom.childNodes[0] | ||
3097 | 704 | if node.localName != "CertificateFile": | ||
3098 | 705 | Error("Certificates.Parse: root not CertificateFile") | ||
3099 | 706 | return None | ||
3100 | 707 | SetFileContents("Certificates.p7m", | ||
3101 | 708 | "MIME-Version: 1.0\n" | ||
3102 | 709 | + "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n" | ||
3103 | 710 | + "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n" | ||
3104 | 711 | + "Content-Transfer-Encoding: base64\n\n" | ||
3105 | 712 | + GetNodeTextData(dom.getElementsByTagName("Data")[0])) | ||
3106 | 713 | if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"): | ||
3107 | 714 | Error("Certificates.Parse: Failed to extract certificates from CMS message.") | ||
3108 | 715 | return self | ||
3109 | 716 | # There may be multiple certificates in this package. Split them. | ||
3110 | 717 | file = open("Certificates.pem") | ||
3111 | 718 | pindex = 1 | ||
3112 | 719 | cindex = 1 | ||
3113 | 720 | output = open("temp.pem", "w") | ||
3114 | 721 | for line in file.readlines(): | ||
3115 | 722 | output.write(line) | ||
3116 | 723 | if re.match(r'[-]+END .*?(KEY|CERTIFICATE)[-]+$',line): | ||
3117 | 724 | output.close() | ||
3118 | 725 | if re.match(r'[-]+END .*?KEY[-]+$',line): | ||
3119 | 726 | os.rename("temp.pem", str(pindex) + ".prv") | ||
3120 | 727 | pindex += 1 | ||
3121 | 728 | else: | ||
3122 | 729 | os.rename("temp.pem", str(cindex) + ".crt") | ||
3123 | 730 | cindex += 1 | ||
3124 | 731 | output = open("temp.pem", "w") | ||
3125 | 732 | output.close() | ||
3126 | 733 | os.remove("temp.pem") | ||
3127 | 734 | keys = dict() | ||
3128 | 735 | index = 1 | ||
3129 | 736 | filename = str(index) + ".crt" | ||
3130 | 737 | while os.path.isfile(filename): | ||
3131 | 738 | thumbprint = os.popen(Openssl + " x509 -in " + filename + " -fingerprint -noout").read().rstrip().split('=')[1].replace(':', '').upper() | ||
3132 | 739 | pubkey=os.popen(Openssl + " x509 -in " + filename + " -pubkey -noout").read() | ||
3133 | 740 | keys[pubkey] = thumbprint | ||
3134 | 741 | os.rename(filename, thumbprint + ".crt") | ||
3135 | 742 | os.chmod(thumbprint + ".crt", 0600) | ||
3136 | 743 | if IsRedHat(): | ||
3137 | 744 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + thumbprint + ".crt") | ||
3138 | 745 | index += 1 | ||
3139 | 746 | filename = str(index) + ".crt" | ||
3140 | 747 | index = 1 | ||
3141 | 748 | filename = str(index) + ".prv" | ||
3142 | 749 | while os.path.isfile(filename): | ||
3143 | 750 | pubkey = os.popen(Openssl + " rsa -in " + filename + " -pubout").read() | ||
3144 | 751 | os.rename(filename, keys[pubkey] + ".prv") | ||
3145 | 752 | os.chmod(keys[pubkey] + ".prv", 0600) | ||
3146 | 753 | if IsRedHat(): | ||
3147 | 754 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + keys[pubkey] + ".prv") | ||
3148 | 755 | index += 1 | ||
3149 | 756 | filename = str(index) + ".prv" | ||
3150 | 757 | return self | ||
3151 | 758 | |||
3152 | 759 | class SharedConfig(object): | ||
3153 | 760 | # | ||
3154 | 761 | # <SharedConfig version="1.0.0.0" goalStateIncarnation="1"> | ||
3155 | 762 | # <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2"> | ||
3156 | 763 | # <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" /> | ||
3157 | 764 | # <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" /> | ||
3158 | 765 | # </Deployment> | ||
3159 | 766 | # <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" /> | ||
3160 | 767 | # <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" /> | ||
3161 | 768 | # <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8"> | ||
3162 | 769 | # <Probes> | ||
3163 | 770 | # <Probe name="MachineRole" /> | ||
3164 | 771 | # <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" /> | ||
3165 | 772 | # <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" /> | ||
3166 | 773 | # </Probes> | ||
3167 | 774 | # </LoadBalancerSettings> | ||
3168 | 775 | # <OutputEndpoints> | ||
3169 | 776 | # <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS"> | ||
3170 | 777 | # <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" /> | ||
3171 | 778 | # </Endpoint> | ||
3172 | 779 | # </OutputEndpoints> | ||
3173 | 780 | # <Instances> | ||
3174 | 781 | # <Instance id="MachineRole_IN_0" address="10.115.153.75"> | ||
3175 | 782 | # <FaultDomains randomId="0" updateId="0" updateCount="0" /> | ||
3176 | 783 | # <InputEndpoints> | ||
3177 | 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"> | ||
3178 | 785 | # <LocalPorts> | ||
3179 | 786 | # <LocalPortRange from="80" to="80" /> | ||
3180 | 787 | # </LocalPorts> | ||
3181 | 788 | # </Endpoint> | ||
3182 | 789 | # <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false"> | ||
3183 | 790 | # <LocalPorts> | ||
3184 | 791 | # <LocalPortRange from="3389" to="3389" /> | ||
3185 | 792 | # </LocalPorts> | ||
3186 | 793 | # </Endpoint> | ||
3187 | 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"> | ||
3188 | 795 | # <LocalPorts> | ||
3189 | 796 | # <LocalPortRange from="20000" to="20000" /> | ||
3190 | 797 | # </LocalPorts> | ||
3191 | 798 | # </Endpoint> | ||
3192 | 799 | # </InputEndpoints> | ||
3193 | 800 | # </Instance> | ||
3194 | 801 | # </Instances> | ||
3195 | 802 | # </SharedConfig> | ||
3196 | 803 | # | ||
3197 | 804 | def __init__(self): | ||
3198 | 805 | self.reinitialize() | ||
3199 | 806 | |||
3200 | 807 | def reinitialize(self): | ||
3201 | 808 | self.Deployment = None | ||
3202 | 809 | self.Incarnation = None | ||
3203 | 810 | self.Role = None | ||
3204 | 811 | self.LoadBalancerSettings = None | ||
3205 | 812 | self.OutputEndpoints = None | ||
3206 | 813 | self.Instances = None | ||
3207 | 814 | |||
3208 | 815 | def Parse(self, xmlText): | ||
3209 | 816 | self.reinitialize() | ||
3210 | 817 | SetFileContents("SharedConfig.xml", xmlText) | ||
3211 | 818 | dom = xml.dom.minidom.parseString(xmlText) | ||
3212 | 819 | for a in [ "SharedConfig", "Deployment", "Service", | ||
3213 | 820 | "ServiceInstance", "Incarnation", "Role", ]: | ||
3214 | 821 | if not dom.getElementsByTagName(a): | ||
3215 | 822 | Error("SharedConfig.Parse: Missing " + a) | ||
3216 | 823 | return None | ||
3217 | 824 | node = dom.childNodes[0] | ||
3218 | 825 | if node.localName != "SharedConfig": | ||
3219 | 826 | Error("SharedConfig.Parse: root not SharedConfig") | ||
3220 | 827 | return None | ||
3221 | 828 | program = Config.get("Role.TopologyConsumer") | ||
3222 | 829 | if program != None: | ||
3223 | 830 | Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"])) | ||
3224 | 831 | return self | ||
3225 | 832 | |||
3226 | 833 | class HostingEnvironmentConfig(object): | ||
3227 | 834 | # | ||
3228 | 835 | # <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1"> | ||
3229 | 836 | # <StoredCertificates> | ||
3230 | 837 | # <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" /> | ||
3231 | 838 | # </StoredCertificates> | ||
3232 | 839 | # <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2"> | ||
3233 | 840 | # <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" /> | ||
3234 | 841 | # <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" /> | ||
3235 | 842 | # </Deployment> | ||
3236 | 843 | # <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" /> | ||
3237 | 844 | # <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" /> | ||
3238 | 845 | # <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip"> | ||
3239 | 846 | # <CAS mode="full" /> | ||
3240 | 847 | # <PrivilegeLevel mode="max" /> | ||
3241 | 848 | # <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties> | ||
3242 | 849 | # </HostingEnvironmentSettings> | ||
3243 | 850 | # <ApplicationSettings> | ||
3244 | 851 | # <Setting name="__ModelData" value="<m role="MachineRole" xmlns="urn:azure:m:v1"><r name="MachineRole"><e name="a" /><e name="b" /><e name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" /><e name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" /></r></m>" /> | ||
3245 | 852 | # <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." /> | ||
3246 | 853 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcN..." /> | ||
3247 | 854 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2022-07-23T23:59:59.0000000-07:00" /> | ||
3248 | 855 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test" /> | ||
3249 | 856 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" /> | ||
3250 | 857 | # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" /> | ||
3251 | 858 | # <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" /> | ||
3252 | 859 | # </ApplicationSettings> | ||
3253 | 860 | # <ResourceReferences> | ||
3254 | 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" /> | ||
3255 | 862 | # </ResourceReferences> | ||
3256 | 863 | # </HostingEnvironmentConfig> | ||
3257 | 864 | # | ||
3258 | 865 | def __init__(self): | ||
3259 | 866 | self.reinitialize() | ||
3260 | 867 | |||
3261 | 868 | def reinitialize(self): | ||
3262 | 869 | self.StoredCertificates = None | ||
3263 | 870 | self.Deployment = None | ||
3264 | 871 | self.Incarnation = None | ||
3265 | 872 | self.Role = None | ||
3266 | 873 | self.HostingEnvironmentSettings = None | ||
3267 | 874 | self.ApplicationSettings = None | ||
3268 | 875 | self.Certificates = None | ||
3269 | 876 | self.ResourceReferences = None | ||
3270 | 877 | |||
3271 | 878 | def Parse(self, xmlText): | ||
3272 | 879 | self.reinitialize() | ||
3273 | 880 | SetFileContents("HostingEnvironmentConfig.xml", xmlText) | ||
3274 | 881 | dom = xml.dom.minidom.parseString(xmlText) | ||
3275 | 882 | for a in [ "HostingEnvironmentConfig", "Deployment", "Service", | ||
3276 | 883 | "ServiceInstance", "Incarnation", "Role", ]: | ||
3277 | 884 | if not dom.getElementsByTagName(a): | ||
3278 | 885 | Error("HostingEnvironmentConfig.Parse: Missing " + a) | ||
3279 | 886 | return None | ||
3280 | 887 | node = dom.childNodes[0] | ||
3281 | 888 | if node.localName != "HostingEnvironmentConfig": | ||
3282 | 889 | Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig") | ||
3283 | 890 | return None | ||
3284 | 891 | self.ApplicationSettings = dom.getElementsByTagName("Setting") | ||
3285 | 892 | self.Certificates = dom.getElementsByTagName("StoredCertificate") | ||
3286 | 893 | return self | ||
3287 | 894 | |||
3288 | 895 | def DecryptPassword(self, e): | ||
3289 | 896 | SetFileContents("password.p7m", | ||
3290 | 897 | "MIME-Version: 1.0\n" | ||
3291 | 898 | + "Content-Disposition: attachment; filename=\"password.p7m\"\n" | ||
3292 | 899 | + "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n" | ||
3293 | 900 | + "Content-Transfer-Encoding: base64\n\n" | ||
3294 | 901 | + textwrap.fill(e, 64)) | ||
3295 | 902 | return os.popen(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem").read() | ||
3296 | 903 | |||
3297 | 904 | def ActivateResourceDisk(self): | ||
3298 | 905 | global DiskActivated | ||
3299 | 906 | if IsWindows(): | ||
3300 | 907 | DiskActivated = True | ||
3301 | 908 | Log("Skipping ActivateResourceDisk on Windows") | ||
3302 | 909 | return | ||
3303 | 910 | format = Config.get("ResourceDisk.Format") | ||
3304 | 911 | if format == None or format.lower().startswith("n"): | ||
3305 | 912 | DiskActivated = True | ||
3306 | 913 | return | ||
3307 | 914 | device = DeviceForIdePort(1) | ||
3308 | 915 | if device == None: | ||
3309 | 916 | Error("ActivateResourceDisk: Unable to detect disk topology.") | ||
3310 | 917 | return | ||
3311 | 918 | device = "/dev/" + device | ||
3312 | 919 | for entry in os.popen("mount").read().split(): | ||
3313 | 920 | if entry.startswith(device + "1"): | ||
3314 | 921 | Log("ActivateResourceDisk: " + device + "1 is already mounted.") | ||
3315 | 922 | DiskActivated = True | ||
3316 | 923 | return | ||
3317 | 924 | mountpoint = Config.get("ResourceDisk.MountPoint") | ||
3318 | 925 | if mountpoint == None: | ||
3319 | 926 | mountpoint = "/mnt/resource" | ||
3320 | 927 | CreateDir(mountpoint, "root", 0755) | ||
3321 | 928 | fs = Config.get("ResourceDisk.Filesystem") | ||
3322 | 929 | if fs == None: | ||
3323 | 930 | fs = "ext3" | ||
3324 | 931 | if os.popen("sfdisk -q -c " + device + " 1").read().rstrip() == "7" and fs != "ntfs": | ||
3325 | 932 | Run("sfdisk -c " + device + " 1 83") | ||
3326 | 933 | Run("mkfs." + fs + " " + device + "1") | ||
3327 | 934 | if Run("mount " + device + "1 " + mountpoint): | ||
3328 | 935 | Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).") | ||
3329 | 936 | return | ||
3330 | 937 | Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs) | ||
3331 | 938 | DiskActivated = True | ||
3332 | 939 | swap = Config.get("ResourceDisk.EnableSwap") | ||
3333 | 940 | if swap == None or swap.lower().startswith("n"): | ||
3334 | 941 | return | ||
3335 | 942 | sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024 | ||
3336 | 943 | if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024): | ||
3337 | 944 | os.remove(mountpoint + "/swapfile") | ||
3338 | 945 | if not os.path.isfile(mountpoint + "/swapfile"): | ||
3339 | 946 | Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB)) | ||
3340 | 947 | Run("mkswap " + mountpoint + "/swapfile") | ||
3341 | 948 | if not Run("swapon " + mountpoint + "/swapfile"): | ||
3342 | 949 | Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile") | ||
3343 | 950 | else: | ||
3344 | 951 | Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile") | ||
3345 | 952 | |||
3346 | 953 | def Process(self): | ||
3347 | 954 | if DiskActivated == False: | ||
3348 | 955 | diskThread = threading.Thread(target = self.ActivateResourceDisk) | ||
3349 | 956 | diskThread.start() | ||
3350 | 957 | User = None | ||
3351 | 958 | Pass = None | ||
3352 | 959 | Expiration = None | ||
3353 | 960 | Thumbprint = None | ||
3354 | 961 | for b in self.ApplicationSettings: | ||
3355 | 962 | sname = b.getAttribute("name") | ||
3356 | 963 | svalue = b.getAttribute("value") | ||
3357 | 964 | if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword": | ||
3358 | 965 | Pass = self.DecryptPassword(svalue) | ||
3359 | 966 | elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername": | ||
3360 | 967 | User = svalue | ||
3361 | 968 | elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration": | ||
3362 | 969 | Expiration = svalue | ||
3363 | 970 | elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption": | ||
3364 | 971 | Thumbprint = svalue.split(':')[1].upper() | ||
3365 | 972 | if User != None and Pass != None: | ||
3366 | 973 | if User != "root" and User != "" and Pass != "": | ||
3367 | 974 | CreateAccount(User, Pass, Expiration, Thumbprint) | ||
3368 | 975 | else: | ||
3369 | 976 | Error("Not creating user account: " + User) | ||
3370 | 977 | for c in self.Certificates: | ||
3371 | 978 | cname = c.getAttribute("name") | ||
3372 | 979 | csha1 = c.getAttribute("certificateId").split(':')[1].upper() | ||
3373 | 980 | cpath = c.getAttribute("storeName") | ||
3374 | 981 | clevel = c.getAttribute("configurationLevel") | ||
3375 | 982 | if os.path.isfile(csha1 + ".prv"): | ||
3376 | 983 | Log("Private key with thumbprint: " + csha1 + " was retrieved.") | ||
3377 | 984 | if os.path.isfile(csha1 + ".crt"): | ||
3378 | 985 | Log("Public cert with thumbprint: " + csha1 + " was retrieved.") | ||
3379 | 986 | program = Config.get("Role.ConfigurationConsumer") | ||
3380 | 987 | if program != None: | ||
3381 | 988 | Children.append(subprocess.Popen([program, LibDir + "/HostingEnvironmentConfig.xml"])) | ||
3382 | 989 | |||
3383 | 990 | class GoalState(Util): | ||
3384 | 991 | # | ||
3385 | 992 | # <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd"> | ||
3386 | 993 | # <Version>2010-12-15</Version> | ||
3387 | 994 | # <Incarnation>1</Incarnation> | ||
3388 | 995 | # <Machine> | ||
3389 | 996 | # <ExpectedState>Started</ExpectedState> | ||
3390 | 997 | # <LBProbePorts> | ||
3391 | 998 | # <Port>16001</Port> | ||
3392 | 999 | # </LBProbePorts> | ||
3393 | 1000 | # </Machine> | ||
3394 | 1001 | # <Container> | ||
3395 | 1002 | # <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId> | ||
3396 | 1003 | # <RoleInstanceList> | ||
3397 | 1004 | # <RoleInstance> | ||
3398 | 1005 | # <InstanceId>MachineRole_IN_0</InstanceId> | ||
3399 | 1006 | # <State>Started</State> | ||
3400 | 1007 | # <Configuration> | ||
3401 | 1008 | # <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=hostingEnvironmentConfig&incarnation=1</HostingEnvironmentConfig> | ||
3402 | 1009 | # <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=sharedConfig&incarnation=1</SharedConfig> | ||
3403 | 1010 | # <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&incarnation=1</Certificates> | ||
3404 | 1011 | # </Configuration> | ||
3405 | 1012 | # </RoleInstance> | ||
3406 | 1013 | # </RoleInstanceList> | ||
3407 | 1014 | # </Container> | ||
3408 | 1015 | # </GoalState> | ||
3409 | 1016 | # | ||
3410 | 1017 | # There is only one Role for VM images. | ||
3411 | 1018 | # | ||
3412 | 1019 | # Of primary interest is: | ||
3413 | 1020 | # Machine/ExpectedState -- this is how shutdown is requested | ||
3414 | 1021 | # LBProbePorts -- an http server needs to run here | ||
3415 | 1022 | # We also note Container/ContainerID and RoleInstance/InstanceId to form the health report. | ||
3416 | 1023 | # And of course, Incarnation | ||
3417 | 1024 | # | ||
3418 | 1025 | def __init__(self, Agent): | ||
3419 | 1026 | self.Agent = Agent | ||
3420 | 1027 | self.Endpoint = Agent.Endpoint | ||
3421 | 1028 | self.TransportCert = Agent.TransportCert | ||
3422 | 1029 | self.reinitialize() | ||
3423 | 1030 | |||
3424 | 1031 | def reinitialize(self): | ||
3425 | 1032 | self.Incarnation = None # integer | ||
3426 | 1033 | self.ExpectedState = None # "Started" or "Stopped" | ||
3427 | 1034 | self.HostingEnvironmentConfigUrl = None | ||
3428 | 1035 | self.HostingEnvironmentConfigXml = None | ||
3429 | 1036 | self.HostingEnvironmentConfig = None | ||
3430 | 1037 | self.SharedConfigUrl = None | ||
3431 | 1038 | self.SharedConfigXml = None | ||
3432 | 1039 | self.SharedConfig = None | ||
3433 | 1040 | self.CertificatesUrl = None | ||
3434 | 1041 | self.CertificatesXml = None | ||
3435 | 1042 | self.Certificates = None | ||
3436 | 1043 | self.RoleInstanceId = None | ||
3437 | 1044 | self.ContainerId = None | ||
3438 | 1045 | self.LoadBalancerProbePort = None # integer, ?list of integers | ||
3439 | 1046 | |||
3440 | 1047 | def Parse(self, xmlText): | ||
3441 | 1048 | self.reinitialize() | ||
3442 | 1049 | node = xml.dom.minidom.parseString(xmlText).childNodes[0] | ||
3443 | 1050 | if node.localName != "GoalState": | ||
3444 | 1051 | Error("GoalState.Parse: root not GoalState") | ||
3445 | 1052 | return None | ||
3446 | 1053 | for a in node.childNodes: | ||
3447 | 1054 | if a.nodeType == node.ELEMENT_NODE: | ||
3448 | 1055 | if a.localName == "Incarnation": | ||
3449 | 1056 | self.Incarnation = GetNodeTextData(a) | ||
3450 | 1057 | elif a.localName == "Machine": | ||
3451 | 1058 | for b in a.childNodes: | ||
3452 | 1059 | if b.nodeType == node.ELEMENT_NODE: | ||
3453 | 1060 | if b.localName == "ExpectedState": | ||
3454 | 1061 | self.ExpectedState = GetNodeTextData(b) | ||
3455 | 1062 | Log("ExpectedState: " + self.ExpectedState) | ||
3456 | 1063 | elif b.localName == "LBProbePorts": | ||
3457 | 1064 | for c in b.childNodes: | ||
3458 | 1065 | if c.nodeType == node.ELEMENT_NODE and c.localName == "Port": | ||
3459 | 1066 | self.LoadBalancerProbePort = int(GetNodeTextData(c)) | ||
3460 | 1067 | elif a.localName == "Container": | ||
3461 | 1068 | for b in a.childNodes: | ||
3462 | 1069 | if b.nodeType == node.ELEMENT_NODE: | ||
3463 | 1070 | if b.localName == "ContainerId": | ||
3464 | 1071 | self.ContainerId = GetNodeTextData(b) | ||
3465 | 1072 | Log("ContainerId: " + self.ContainerId) | ||
3466 | 1073 | elif b.localName == "RoleInstanceList": | ||
3467 | 1074 | for c in b.childNodes: | ||
3468 | 1075 | if c.localName == "RoleInstance": | ||
3469 | 1076 | for d in c.childNodes: | ||
3470 | 1077 | if d.nodeType == node.ELEMENT_NODE: | ||
3471 | 1078 | if d.localName == "InstanceId": | ||
3472 | 1079 | self.RoleInstanceId = GetNodeTextData(d) | ||
3473 | 1080 | Log("RoleInstanceId: " + self.RoleInstanceId) | ||
3474 | 1081 | elif d.localName == "State": | ||
3475 | 1082 | pass | ||
3476 | 1083 | elif d.localName == "Configuration": | ||
3477 | 1084 | for e in d.childNodes: | ||
3478 | 1085 | if e.nodeType == node.ELEMENT_NODE: | ||
3479 | 1086 | if e.localName == "HostingEnvironmentConfig": | ||
3480 | 1087 | self.HostingEnvironmentConfigUrl = GetNodeTextData(e) | ||
3481 | 1088 | LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl) | ||
3482 | 1089 | self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl) | ||
3483 | 1090 | self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml) | ||
3484 | 1091 | elif e.localName == "SharedConfig": | ||
3485 | 1092 | self.SharedConfigUrl = GetNodeTextData(e) | ||
3486 | 1093 | LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl) | ||
3487 | 1094 | self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl) | ||
3488 | 1095 | self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml) | ||
3489 | 1096 | elif e.localName == "Certificates": | ||
3490 | 1097 | self.CertificatesUrl = GetNodeTextData(e) | ||
3491 | 1098 | LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl) | ||
3492 | 1099 | self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert) | ||
3493 | 1100 | self.Certificates = Certificates().Parse(self.CertificatesXml) | ||
3494 | 1101 | if self.Incarnation == None: | ||
3495 | 1102 | Error("GoalState.Parse: Incarnation missing") | ||
3496 | 1103 | return None | ||
3497 | 1104 | if self.ExpectedState == None: | ||
3498 | 1105 | Error("GoalState.Parse: ExpectedState missing") | ||
3499 | 1106 | return None | ||
3500 | 1107 | if self.RoleInstanceId == None: | ||
3501 | 1108 | Error("GoalState.Parse: RoleInstanceId missing") | ||
3502 | 1109 | return None | ||
3503 | 1110 | if self.ContainerId == None: | ||
3504 | 1111 | Error("GoalState.Parse: ContainerId missing") | ||
3505 | 1112 | return None | ||
3506 | 1113 | SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText) | ||
3507 | 1114 | return self | ||
3508 | 1115 | |||
3509 | 1116 | def Process(self): | ||
3510 | 1117 | self.HostingEnvironmentConfig.Process() | ||
3511 | 1118 | |||
3512 | 1119 | class OvfEnv(object): | ||
3513 | 1120 | # | ||
3514 | 1121 | # <?xml version="1.0" encoding="utf-8"?> | ||
3515 | 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"> | ||
3516 | 1123 | # <wa:ProvisioningSection> | ||
3517 | 1124 | # <wa:Version>1.0</wa:Version> | ||
3518 | 1125 | # <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> | ||
3519 | 1126 | # <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType> | ||
3520 | 1127 | # <HostName>HostName</HostName> | ||
3521 | 1128 | # <UserName>UserName</UserName> | ||
3522 | 1129 | # <UserPassword>UserPassword</UserPassword> | ||
3523 | 1130 | # <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication> | ||
3524 | 1131 | # <SSH> | ||
3525 | 1132 | # <PublicKeys> | ||
3526 | 1133 | # <PublicKey> | ||
3527 | 1134 | # <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> | ||
3528 | 1135 | # <Path>$HOME/UserName/.ssh/authorized_keys</Path> | ||
3529 | 1136 | # </PublicKey> | ||
3530 | 1137 | # </PublicKeys> | ||
3531 | 1138 | # <KeyPairs> | ||
3532 | 1139 | # <KeyPair> | ||
3533 | 1140 | # <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> | ||
3534 | 1141 | # <Path>$HOME/UserName/.ssh/id_rsa</Path> | ||
3535 | 1142 | # </KeyPair> | ||
3536 | 1143 | # </KeyPairs> | ||
3537 | 1144 | # </SSH> | ||
3538 | 1145 | # </LinuxProvisioningConfigurationSet> | ||
3539 | 1146 | # </wa:ProvisioningSection> | ||
3540 | 1147 | # </Environment> | ||
3541 | 1148 | # | ||
3542 | 1149 | def __init__(self): | ||
3543 | 1150 | self.reinitialize() | ||
3544 | 1151 | |||
3545 | 1152 | def reinitialize(self): | ||
3546 | 1153 | self.WaNs = "http://schemas.microsoft.com/windowsazure" | ||
3547 | 1154 | self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1" | ||
3548 | 1155 | self.MajorVersion = 1 | ||
3549 | 1156 | self.MinorVersion = 0 | ||
3550 | 1157 | self.ComputerName = None | ||
3551 | 1158 | self.AdminPassword = None | ||
3552 | 1159 | self.UserName = None | ||
3553 | 1160 | self.UserPassword = None | ||
3554 | 1161 | self.DisableSshPasswordAuthentication = True | ||
3555 | 1162 | self.SshPublicKeys = [] | ||
3556 | 1163 | self.SshKeyPairs = [] | ||
3557 | 1164 | |||
3558 | 1165 | def Parse(self, xmlText): | ||
3559 | 1166 | self.reinitialize() | ||
3560 | 1167 | dom = xml.dom.minidom.parseString(xmlText) | ||
3561 | 1168 | if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1: | ||
3562 | 1169 | Error("Unable to parse OVF XML.") | ||
3563 | 1170 | section = None | ||
3564 | 1171 | newer = False | ||
3565 | 1172 | for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"): | ||
3566 | 1173 | for n in p.childNodes: | ||
3567 | 1174 | if n.localName == "Version": | ||
3568 | 1175 | verparts = GetNodeTextData(n).split('.') | ||
3569 | 1176 | major = int(verparts[0]) | ||
3570 | 1177 | minor = int(verparts[1]) | ||
3571 | 1178 | if major > self.MajorVersion: | ||
3572 | 1179 | newer = True | ||
3573 | 1180 | if major != self.MajorVersion: | ||
3574 | 1181 | break | ||
3575 | 1182 | if minor > self.MinorVersion: | ||
3576 | 1183 | newer = True | ||
3577 | 1184 | section = p | ||
3578 | 1185 | if newer == True: | ||
3579 | 1186 | Warn("Newer provisioning configuration detected. Please consider updating waagent.") | ||
3580 | 1187 | if section == None: | ||
3581 | 1188 | Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion)) | ||
3582 | 1189 | return None | ||
3583 | 1190 | self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0]) | ||
3584 | 1191 | self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0]) | ||
3585 | 1192 | try: | ||
3586 | 1193 | self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0]) | ||
3587 | 1194 | except: | ||
3588 | 1195 | pass | ||
3589 | 1196 | disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication") | ||
3590 | 1197 | if len(disableSshPass) != 0: | ||
3591 | 1198 | self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true") | ||
3592 | 1199 | for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"): | ||
3593 | 1200 | fp = None | ||
3594 | 1201 | path = None | ||
3595 | 1202 | for c in pkey.childNodes: | ||
3596 | 1203 | if c.localName == "Fingerprint": | ||
3597 | 1204 | fp = GetNodeTextData(c).upper() | ||
3598 | 1205 | if c.localName == "Path": | ||
3599 | 1206 | path = GetNodeTextData(c) | ||
3600 | 1207 | self.SshPublicKeys += [[fp, path]] | ||
3601 | 1208 | for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"): | ||
3602 | 1209 | fp = None | ||
3603 | 1210 | path = None | ||
3604 | 1211 | for c in keyp.childNodes: | ||
3605 | 1212 | if c.localName == "Fingerprint": | ||
3606 | 1213 | fp = GetNodeTextData(c).upper() | ||
3607 | 1214 | if c.localName == "Path": | ||
3608 | 1215 | path = GetNodeTextData(c) | ||
3609 | 1216 | self.SshKeyPairs += [[fp, path]] | ||
3610 | 1217 | return self | ||
3611 | 1218 | |||
3612 | 1219 | def PrepareDir(self, filepath): | ||
3613 | 1220 | home = GetHome() | ||
3614 | 1221 | # Expand HOME variable if present in path | ||
3615 | 1222 | path = os.path.normpath(filepath.replace("$HOME", home)) | ||
3616 | 1223 | if (path.startswith("/") == False) or (path.endswith("/") == True): | ||
3617 | 1224 | return None | ||
3618 | 1225 | dir = path.rsplit('/', 1)[0] | ||
3619 | 1226 | if dir != "": | ||
3620 | 1227 | CreateDir(dir, "root", 0700) | ||
3621 | 1228 | if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")): | ||
3622 | 1229 | ChangeOwner(dir, self.UserName) | ||
3623 | 1230 | return path | ||
3624 | 1231 | |||
3625 | 1232 | def NumberToBytes(self, i): | ||
3626 | 1233 | result = [] | ||
3627 | 1234 | while i: | ||
3628 | 1235 | result.append(chr(i & 0xFF)) | ||
3629 | 1236 | i >>= 8 | ||
3630 | 1237 | result.reverse() | ||
3631 | 1238 | return ''.join(result) | ||
3632 | 1239 | |||
3633 | 1240 | def BitsToString(self, a): | ||
3634 | 1241 | index=7 | ||
3635 | 1242 | s = "" | ||
3636 | 1243 | c = 0 | ||
3637 | 1244 | for bit in a: | ||
3638 | 1245 | c = c | (bit << index) | ||
3639 | 1246 | index = index - 1 | ||
3640 | 1247 | if index == -1: | ||
3641 | 1248 | s = s + struct.pack('>B', c) | ||
3642 | 1249 | c = 0 | ||
3643 | 1250 | index = 7 | ||
3644 | 1251 | return s | ||
3645 | 1252 | |||
3646 | 1253 | def OpensslToSsh(self, file): | ||
3647 | 1254 | from pyasn1.codec.der import decoder as der_decoder | ||
3648 | 1255 | try: | ||
3649 | 1256 | f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0] | ||
3650 | 1257 | k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0] | ||
3651 | 1258 | n=k[0] | ||
3652 | 1259 | e=k[1] | ||
3653 | 1260 | keydata="" | ||
3654 | 1261 | keydata += struct.pack('>I',len("ssh-rsa")) | ||
3655 | 1262 | keydata += "ssh-rsa" | ||
3656 | 1263 | keydata += struct.pack('>I',len(self.NumberToBytes(e))) | ||
3657 | 1264 | keydata += self.NumberToBytes(e) | ||
3658 | 1265 | keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1) | ||
3659 | 1266 | keydata += "\0" | ||
3660 | 1267 | keydata += self.NumberToBytes(n) | ||
3661 | 1268 | except Exception, e: | ||
3662 | 1269 | print("OpensslToSsh: Exception " + str(e)) | ||
3663 | 1270 | return None | ||
3664 | 1271 | return "ssh-rsa " + base64.b64encode(keydata) + "\n" | ||
3665 | 1272 | |||
3666 | 1273 | def Process(self): | ||
3667 | 1274 | error = None | ||
3668 | 1275 | error=WaAgent.EnvMonitor.SetHostName(self.ComputerName) | ||
3669 | 1276 | if error: return error | ||
3670 | 1277 | if self.DisableSshPasswordAuthentication: | ||
3671 | 1278 | filepath = "/etc/ssh/sshd_config" | ||
3672 | 1279 | # Disable RFC 4252 and RFC 4256 authentication schemes. | ||
3673 | 1280 | ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not | ||
3674 | 1281 | (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")), | ||
3675 | 1282 | GetFileContents(filepath).split('\n'))) + "PasswordAuthentication no\nChallengeResponseAuthentication no\n") | ||
3676 | 1283 | Log("Disabled SSH password-based authentication methods.") | ||
3677 | 1284 | if self.AdminPassword != None: | ||
3678 | 1285 | os.popen("chpasswd", "w").write("root:" + self.AdminPassword + "\n") | ||
3679 | 1286 | if self.UserName != None: | ||
3680 | 1287 | error = CreateAccount(self.UserName, self.UserPassword, None, None) | ||
3681 | 1288 | sel = os.popen("getenforce").read().startswith("Enforcing") | ||
3682 | 1289 | if sel == True and IsRedHat(): | ||
3683 | 1290 | Run("setenforce 0") | ||
3684 | 1291 | home = GetHome() | ||
3685 | 1292 | for pkey in self.SshPublicKeys: | ||
3686 | 1293 | if not os.path.isfile(pkey[0] + ".crt"): | ||
3687 | 1294 | Error("PublicKey not found: " + pkey[0]) | ||
3688 | 1295 | error = "Failed to deploy public key (0x09)." | ||
3689 | 1296 | continue | ||
3690 | 1297 | path = self.PrepareDir(pkey[1]) | ||
3691 | 1298 | if path == None: | ||
3692 | 1299 | Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0]) | ||
3693 | 1300 | error = "Invalid path for public key (0x03)." | ||
3694 | 1301 | continue | ||
3695 | 1302 | Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub") | ||
3696 | 1303 | if IsRedHat(): | ||
3697 | 1304 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + pkey[0] + ".pub") | ||
3698 | 1305 | if IsUbuntu(): | ||
3699 | 1306 | # Only supported in new SSH releases | ||
3700 | 1307 | Run("ssh-keygen -i -m PKCS8 -f " + pkey[0] + ".pub >> " + path) | ||
3701 | 1308 | else: | ||
3702 | 1309 | SshPubKey = self.OpensslToSsh(pkey[0] + ".pub") | ||
3703 | 1310 | if SshPubKey != None: | ||
3704 | 1311 | AppendFileContents(path, SshPubKey) | ||
3705 | 1312 | else: | ||
3706 | 1313 | Error("Failed: " + pkey[0] + ".crt -> " + path) | ||
3707 | 1314 | error = "Failed to deploy public key (0x04)." | ||
3708 | 1315 | if IsRedHat(): | ||
3709 | 1316 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path) | ||
3710 | 1317 | if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")): | ||
3711 | 1318 | ChangeOwner(path, self.UserName) | ||
3712 | 1319 | for keyp in self.SshKeyPairs: | ||
3713 | 1320 | if not os.path.isfile(keyp[0] + ".prv"): | ||
3714 | 1321 | Error("KeyPair not found: " + keyp[0]) | ||
3715 | 1322 | error = "Failed to deploy key pair (0x0A)." | ||
3716 | 1323 | continue | ||
3717 | 1324 | path = self.PrepareDir(keyp[1]) | ||
3718 | 1325 | if path == None: | ||
3719 | 1326 | Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0]) | ||
3720 | 1327 | error = "Invalid path for key pair (0x05)." | ||
3721 | 1328 | continue | ||
3722 | 1329 | SetFileContents(path, GetFileContents(keyp[0] + ".prv")) | ||
3723 | 1330 | os.chmod(path, 0600) | ||
3724 | 1331 | Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub") | ||
3725 | 1332 | if IsRedHat(): | ||
3726 | 1333 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path) | ||
3727 | 1334 | Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path + ".pub") | ||
3728 | 1335 | if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")): | ||
3729 | 1336 | ChangeOwner(path, self.UserName) | ||
3730 | 1337 | ChangeOwner(path + ".pub", self.UserName) | ||
3731 | 1338 | if sel == True and IsRedHat(): | ||
3732 | 1339 | Run("setenforce 1") | ||
3733 | 1340 | while not WaAgent.EnvMonitor.IsNamePublished(): | ||
3734 | 1341 | time.sleep(1) | ||
3735 | 1342 | ReloadSshd() | ||
3736 | 1343 | return error | ||
3737 | 1344 | |||
3738 | 1345 | def UpdateAndPublishHostNameCommon(name): | ||
3739 | 1346 | # RedHat | ||
3740 | 1347 | if IsRedHat(): | ||
3741 | 1348 | filepath = "/etc/sysconfig/network" | ||
3742 | 1349 | if os.path.isfile(filepath): | ||
3743 | 1350 | ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n" | ||
3744 | 1351 | + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n')))) | ||
3745 | 1352 | |||
3746 | 1353 | for ethernetInterface in PossibleEthernetInterfaces: | ||
3747 | 1354 | filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface | ||
3748 | 1355 | if os.path.isfile(filepath): | ||
3749 | 1356 | ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n" | ||
3750 | 1357 | + "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n')))) | ||
3751 | 1358 | |||
3752 | 1359 | # Debian | ||
3753 | 1360 | if IsDebian(): | ||
3754 | 1361 | SetFileContents("/etc/hostname", name) | ||
3755 | 1362 | |||
3756 | 1363 | for filepath in EtcDhcpClientConfFiles: | ||
3757 | 1364 | if os.path.isfile(filepath): | ||
3758 | 1365 | ReplaceFileContentsAtomic(filepath, "send host-name \"" + name + "\";\n" | ||
3759 | 1366 | + "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents(filepath).split('\n')))) | ||
3760 | 1367 | |||
3761 | 1368 | # Suse | ||
3762 | 1369 | if IsSuse(): | ||
3763 | 1370 | SetFileContents("/etc/HOSTNAME", name) | ||
3764 | 1371 | |||
3765 | 1372 | class Agent(Util): | ||
3766 | 1373 | def __init__(self): | ||
3767 | 1374 | self.GoalState = None | ||
3768 | 1375 | self.Endpoint = None | ||
3769 | 1376 | self.LoadBalancerProbeServer = None | ||
3770 | 1377 | self.HealthReportCounter = 0 | ||
3771 | 1378 | self.TransportCert = "" | ||
3772 | 1379 | self.EnvMonitor = None | ||
3773 | 1380 | self.SendData = None | ||
3774 | 1381 | self.DhcpResponse = None | ||
3775 | 1382 | |||
3776 | 1383 | def CheckVersions(self): | ||
3777 | 1384 | #<?xml version="1.0" encoding="utf-8"?> | ||
3778 | 1385 | #<Versions> | ||
3779 | 1386 | # <Preferred> | ||
3780 | 1387 | # <Version>2010-12-15</Version> | ||
3781 | 1388 | # </Preferred> | ||
3782 | 1389 | # <Supported> | ||
3783 | 1390 | # <Version>2010-12-15</Version> | ||
3784 | 1391 | # <Version>2010-28-10</Version> | ||
3785 | 1392 | # </Supported> | ||
3786 | 1393 | #</Versions> | ||
3787 | 1394 | global ProtocolVersion | ||
3788 | 1395 | protocolVersionSeen = False | ||
3789 | 1396 | node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0] | ||
3790 | 1397 | if node.localName != "Versions": | ||
3791 | 1398 | Error("CheckVersions: root not Versions") | ||
3792 | 1399 | return False | ||
3793 | 1400 | for a in node.childNodes: | ||
3794 | 1401 | if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported": | ||
3795 | 1402 | for b in a.childNodes: | ||
3796 | 1403 | if b.nodeType == node.ELEMENT_NODE and b.localName == "Version": | ||
3797 | 1404 | v = GetNodeTextData(b) | ||
3798 | 1405 | LogIfVerbose("Fabric supported wire protocol version: " + v) | ||
3799 | 1406 | if v == ProtocolVersion: | ||
3800 | 1407 | protocolVersionSeen = True | ||
3801 | 1408 | if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred": | ||
3802 | 1409 | v = GetNodeTextData(a.getElementsByTagName("Version")[0]) | ||
3803 | 1410 | LogIfVerbose("Fabric preferred wire protocol version: " + v) | ||
3804 | 1411 | if ProtocolVersion < v: | ||
3805 | 1412 | Warn("Newer wire protocol version detected. Please consider updating waagent.") | ||
3806 | 1413 | if not protocolVersionSeen: | ||
3807 | 1414 | Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.") | ||
3808 | 1415 | ProtocolVersion = "2011-08-31" | ||
3809 | 1416 | Log("Negotiated wire protocol version: " + ProtocolVersion) | ||
3810 | 1417 | return True | ||
3811 | 1418 | |||
3812 | 1419 | def Unpack(self, buffer, offset, range): | ||
3813 | 1420 | result = 0 | ||
3814 | 1421 | for i in range: | ||
3815 | 1422 | result = (result << 8) | Ord(buffer[offset + i]) | ||
3816 | 1423 | return result | ||
3817 | 1424 | |||
3818 | 1425 | def UnpackLittleEndian(self, buffer, offset, length): | ||
3819 | 1426 | return self.Unpack(buffer, offset, range(length - 1, -1, -1)) | ||
3820 | 1427 | |||
3821 | 1428 | def UnpackBigEndian(self, buffer, offset, length): | ||
3822 | 1429 | return self.Unpack(buffer, offset, range(0, length)) | ||
3823 | 1430 | |||
3824 | 1431 | def HexDump3(self, buffer, offset, length): | ||
3825 | 1432 | return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]]) | ||
3826 | 1433 | |||
3827 | 1434 | def HexDump2(self, buffer): | ||
3828 | 1435 | return self.HexDump3(buffer, 0, len(buffer)) | ||
3829 | 1436 | |||
3830 | 1437 | def BuildDhcpRequest(self): | ||
3831 | 1438 | # | ||
3832 | 1439 | # typedef struct _DHCP { | ||
3833 | 1440 | # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */ | ||
3834 | 1441 | # UINT8 HardwareAddressType; /* htype: ethernet */ | ||
3835 | 1442 | # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */ | ||
3836 | 1443 | # UINT8 Hops; /* hops: 0 */ | ||
3837 | 1444 | # UINT8 TransactionID[4]; /* xid: random */ | ||
3838 | 1445 | # UINT8 Seconds[2]; /* secs: 0 */ | ||
3839 | 1446 | # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */ | ||
3840 | 1447 | # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */ | ||
3841 | 1448 | # UINT8 YourIpAddress[4]; /* yiaddr: 0 */ | ||
3842 | 1449 | # UINT8 ServerIpAddress[4]; /* siaddr: 0 */ | ||
3843 | 1450 | # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */ | ||
3844 | 1451 | # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */ | ||
3845 | 1452 | # UINT8 ServerName[64]; /* sname: 0 */ | ||
3846 | 1453 | # UINT8 BootFileName[128]; /* file: 0 */ | ||
3847 | 1454 | # UINT8 MagicCookie[4]; /* 99 130 83 99 */ | ||
3848 | 1455 | # /* 0x63 0x82 0x53 0x63 */ | ||
3849 | 1456 | # /* options -- hard code ours */ | ||
3850 | 1457 | # | ||
3851 | 1458 | # UINT8 MessageTypeCode; /* 53 */ | ||
3852 | 1459 | # UINT8 MessageTypeLength; /* 1 */ | ||
3853 | 1460 | # UINT8 MessageType; /* 1 for DISCOVER */ | ||
3854 | 1461 | # UINT8 End; /* 255 */ | ||
3855 | 1462 | # } DHCP; | ||
3856 | 1463 | # | ||
3857 | 1464 | |||
3858 | 1465 | # tuple of 244 zeros | ||
3859 | 1466 | # (struct.pack_into would be good here, but requires Python 2.5) | ||
3860 | 1467 | sendData = [0] * 244 | ||
3861 | 1468 | |||
3862 | 1469 | transactionID = os.urandom(4) | ||
3863 | 1470 | macAddress = GetMacAddress() | ||
3864 | 1471 | |||
3865 | 1472 | # Opcode = 1 | ||
3866 | 1473 | # HardwareAddressType = 1 (ethernet/MAC) | ||
3867 | 1474 | # HardwareAddressLength = 6 (ethernet/MAC/48 bits) | ||
3868 | 1475 | for a in range(0, 3): | ||
3869 | 1476 | sendData[a] = [1, 1, 6][a] | ||
3870 | 1477 | |||
3871 | 1478 | # fill in transaction id (random number to ensure response matches request) | ||
3872 | 1479 | for a in range(0, 4): | ||
3873 | 1480 | sendData[4 + a] = Ord(transactionID[a]) | ||
3874 | 1481 | |||
3875 | 1482 | LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4))) | ||
3876 | 1483 | |||
3877 | 1484 | # fill in ClientHardwareAddress | ||
3878 | 1485 | for a in range(0, 6): | ||
3879 | 1486 | sendData[0x1C + a] = Ord(macAddress[a]) | ||
3880 | 1487 | |||
3881 | 1488 | # DHCP Magic Cookie: 99, 130, 83, 99 | ||
3882 | 1489 | # MessageTypeCode = 53 DHCP Message Type | ||
3883 | 1490 | # MessageTypeLength = 1 | ||
3884 | 1491 | # MessageType = DHCPDISCOVER | ||
3885 | 1492 | # End = 255 DHCP_END | ||
3886 | 1493 | for a in range(0, 8): | ||
3887 | 1494 | sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a] | ||
3888 | 1495 | return array.array("c", map(chr, sendData)) | ||
3889 | 1496 | |||
3890 | 1497 | def IntegerToIpAddressV4String(self, a): | ||
3891 | 1498 | return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF) | ||
3892 | 1499 | |||
3893 | 1500 | def RouteAdd(self, net, mask, gateway): | ||
3894 | 1501 | if IsWindows(): | ||
3895 | 1502 | return | ||
3896 | 1503 | net = self.IntegerToIpAddressV4String(net) | ||
3897 | 1504 | mask = self.IntegerToIpAddressV4String(mask) | ||
3898 | 1505 | gateway = self.IntegerToIpAddressV4String(gateway) | ||
3899 | 1506 | Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway) | ||
3900 | 1507 | |||
3901 | 1508 | def HandleDhcpResponse(self, sendData, receiveBuffer): | ||
3902 | 1509 | LogIfVerbose("HandleDhcpResponse") | ||
3903 | 1510 | bytesReceived = len(receiveBuffer) | ||
3904 | 1511 | if bytesReceived < 0xF6: | ||
3905 | 1512 | Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived)) | ||
3906 | 1513 | return None | ||
3907 | 1514 | |||
3908 | 1515 | LogIfVerbose("BytesReceived: " + hex(bytesReceived)) | ||
3909 | 1516 | LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived)) | ||
3910 | 1517 | |||
3911 | 1518 | # check transactionId, cookie, MAC address | ||
3912 | 1519 | # cookie should never mismatch | ||
3913 | 1520 | # transactionId and MAC address may mismatch if we see a response meant from another machine | ||
3914 | 1521 | |||
3915 | 1522 | for offsets in [range(4, 4 + 4), range(0x1C, 0x1C + 6), range(0xEC, 0xEC + 4)]: | ||
3916 | 1523 | for offset in offsets: | ||
3917 | 1524 | sentByte = Ord(sendData[offset]) | ||
3918 | 1525 | receivedByte = Ord(receiveBuffer[offset]) | ||
3919 | 1526 | if sentByte != receivedByte: | ||
3920 | 1527 | LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4)) | ||
3921 | 1528 | LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4)) | ||
3922 | 1529 | LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4)) | ||
3923 | 1530 | LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4)) | ||
3924 | 1531 | LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6)) | ||
3925 | 1532 | LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6)) | ||
3926 | 1533 | LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch") | ||
3927 | 1534 | return None | ||
3928 | 1535 | endpoint = None | ||
3929 | 1536 | |||
3930 | 1537 | # | ||
3931 | 1538 | # Walk all the returned options, parsing out what we need, ignoring the others. | ||
3932 | 1539 | # We need the custom option 245 to find the the endpoint we talk to, | ||
3933 | 1540 | # as well as, to handle some Linux DHCP client incompatibilities, | ||
3934 | 1541 | # options 3 for default gateway and 249 for routes. And 255 is end. | ||
3935 | 1542 | # | ||
3936 | 1543 | |||
3937 | 1544 | i = 0xF0 # offset to first option | ||
3938 | 1545 | while i < bytesReceived: | ||
3939 | 1546 | option = Ord(receiveBuffer[i]) | ||
3940 | 1547 | length = 0 | ||
3941 | 1548 | if (i + 1) < bytesReceived: | ||
3942 | 1549 | length = Ord(receiveBuffer[i + 1]) | ||
3943 | 1550 | LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length)) | ||
3944 | 1551 | if option == 255: | ||
3945 | 1552 | LogIfVerbose("DHCP packet ended at offset " + hex(i)) | ||
3946 | 1553 | break | ||
3947 | 1554 | elif option == 249: | ||
3948 | 1555 | # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx | ||
3949 | 1556 | LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length)) | ||
3950 | 1557 | if length < 5: | ||
3951 | 1558 | Error("Data too small for option " + option) | ||
3952 | 1559 | j = i + 2 | ||
3953 | 1560 | while j < (i + length + 2): | ||
3954 | 1561 | maskLengthBits = Ord(receiveBuffer[j]) | ||
3955 | 1562 | maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3) | ||
3956 | 1563 | mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits)) | ||
3957 | 1564 | j += 1 | ||
3958 | 1565 | net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes) | ||
3959 | 1566 | net <<= (32 - maskLengthBytes * 8) | ||
3960 | 1567 | net &= mask | ||
3961 | 1568 | j += maskLengthBytes | ||
3962 | 1569 | gateway = self.UnpackBigEndian(receiveBuffer, j, 4) | ||
3963 | 1570 | j += 4 | ||
3964 | 1571 | self.RouteAdd(net, mask, gateway) | ||
3965 | 1572 | if j != (i + length + 2): | ||
3966 | 1573 | Error("HandleDhcpResponse: Unable to parse routes") | ||
3967 | 1574 | elif option == 3 or option == 245: | ||
3968 | 1575 | if i + 5 < bytesReceived: | ||
3969 | 1576 | if length != 4: | ||
3970 | 1577 | Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes") | ||
3971 | 1578 | return None | ||
3972 | 1579 | gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4) | ||
3973 | 1580 | IpAddress = self.IntegerToIpAddressV4String(gateway) | ||
3974 | 1581 | if option == 3: | ||
3975 | 1582 | self.RouteAdd(0, 0, gateway) | ||
3976 | 1583 | name = "DefaultGateway" | ||
3977 | 1584 | else: | ||
3978 | 1585 | endpoint = IpAddress | ||
3979 | 1586 | name = "Windows Azure wire protocol endpoint" | ||
3980 | 1587 | LogIfVerbose(name + ": " + IpAddress + " at " + hex(i)) | ||
3981 | 1588 | else: | ||
3982 | 1589 | Error("HandleDhcpResponse: Data too small for option " + option) | ||
3983 | 1590 | else: | ||
3984 | 1591 | LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length)) | ||
3985 | 1592 | i += length + 2 | ||
3986 | 1593 | return endpoint | ||
3987 | 1594 | |||
3988 | 1595 | def DoDhcpWork(self): | ||
3989 | 1596 | # | ||
3990 | 1597 | # Discover the wire server via DHCP option 245. | ||
3991 | 1598 | # And workaround incompatibility with Windows Azure DHCP servers. | ||
3992 | 1599 | # | ||
3993 | 1600 | ShortSleep = False # Sleep 1 second before retrying DHCP queries. | ||
3994 | 1601 | ifname=None | ||
3995 | 1602 | if not IsWindows(): | ||
3996 | 1603 | Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT") | ||
3997 | 1604 | Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT") | ||
3998 | 1605 | |||
3999 | 1606 | sleepDurations = [0, 5, 10, 30, 60, 60, 60, 60] | ||
4000 | 1607 | maxRetry = len(sleepDurations) | ||
4001 | 1608 | lastTry = (maxRetry - 1) | ||
4002 | 1609 | for retry in range(0, maxRetry): | ||
4003 | 1610 | try: | ||
4004 | 1611 | strRetry = str(retry) | ||
4005 | 1612 | prefix = "DoDhcpWork: try=" + strRetry | ||
4006 | 1613 | LogIfVerbose(prefix) | ||
4007 | 1614 | sendData = self.BuildDhcpRequest() | ||
4008 | 1615 | LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData))) | ||
4009 | 1616 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) | ||
4010 | 1617 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) | ||
4011 | 1618 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
4012 | 1619 | missingDefaultRoute = True | ||
4013 | 1620 | try: | ||
4014 | 1621 | for line in os.popen("route -n").read().split('\n'): | ||
4015 | 1622 | if line.startswith("0.0.0.0 "): | ||
4016 | 1623 | missingDefaultRoute = False | ||
4017 | 1624 | except: | ||
4018 | 1625 | pass | ||
4019 | 1626 | if missingDefaultRoute: | ||
4020 | 1627 | # This is required because sending after binding to 0.0.0.0 fails with | ||
4021 | 1628 | # network unreachable when the default gateway is not set up. | ||
4022 | 1629 | for i in PossibleEthernetInterfaces: | ||
4023 | 1630 | try: | ||
4024 | 1631 | if Linux_ioctl_GetIpv4Address(i): | ||
4025 | 1632 | ifname=i | ||
4026 | 1633 | except IOError, e: | ||
4027 | 1634 | pass | ||
4028 | 1635 | Log("DoDhcpWork: Missing default route - adding broadcast route for DHCP.") | ||
4029 | 1636 | Run("route add 255.255.255.255 dev " + ifname) | ||
4030 | 1637 | sock.bind(("0.0.0.0", 68)) | ||
4031 | 1638 | sock.sendto(sendData, ("<broadcast>", 67)) | ||
4032 | 1639 | sock.settimeout(10) | ||
4033 | 1640 | Log("DoDhcpWork: Setting socket.timeout=10, entering recv") | ||
4034 | 1641 | receiveBuffer = sock.recv(1024) | ||
4035 | 1642 | endpoint = self.HandleDhcpResponse(sendData, receiveBuffer) | ||
4036 | 1643 | if endpoint == None: | ||
4037 | 1644 | LogIfVerbose("DoDhcpWork: No endpoint found") | ||
4038 | 1645 | if endpoint != None or retry == lastTry: | ||
4039 | 1646 | if endpoint != None: | ||
4040 | 1647 | self.SendData = sendData | ||
4041 | 1648 | self.DhcpResponse = receiveBuffer | ||
4042 | 1649 | if retry == lastTry: | ||
4043 | 1650 | LogIfVerbose("DoDhcpWork: try=" + strRetry) | ||
4044 | 1651 | return endpoint | ||
4045 | 1652 | sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep] | ||
4046 | 1653 | LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration)) | ||
4047 | 1654 | time.sleep(sleepDuration) | ||
4048 | 1655 | except Exception, e: | ||
4049 | 1656 | ErrorWithPrefix(prefix, str(e)) | ||
4050 | 1657 | ErrorWithPrefix(prefix, traceback.format_exc()) | ||
4051 | 1658 | finally: | ||
4052 | 1659 | sock.close() | ||
4053 | 1660 | if missingDefaultRoute: | ||
4054 | 1661 | #We added this route - delete it | ||
4055 | 1662 | Run("route del 255.255.255.255 dev " + ifname) | ||
4056 | 1663 | Log("DoDhcpWork: Removing broadcast route for DHCP.") | ||
4057 | 1664 | return None | ||
4058 | 1665 | |||
4059 | 1666 | def UpdateAndPublishHostName(self, name): | ||
4060 | 1667 | # Set hostname locally and publish to iDNS | ||
4061 | 1668 | Log("Setting host name: " + name) | ||
4062 | 1669 | UpdateAndPublishHostNameCommon(name) | ||
4063 | 1670 | for ethernetInterface in PossibleEthernetInterfaces: | ||
4064 | 1671 | Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface) | ||
4065 | 1672 | self.RestoreRoutes() | ||
4066 | 1673 | |||
4067 | 1674 | def RestoreRoutes(self): | ||
4068 | 1675 | if self.SendData != None and self.DhcpResponse != None: | ||
4069 | 1676 | self.HandleDhcpResponse(self.SendData, self.DhcpResponse) | ||
4070 | 1677 | |||
4071 | 1678 | def UpdateGoalState(self): | ||
4072 | 1679 | goalStateXml = None | ||
4073 | 1680 | maxRetry = 9 | ||
4074 | 1681 | log = NoLog | ||
4075 | 1682 | for retry in range(1, maxRetry + 1): | ||
4076 | 1683 | strRetry = str(retry) | ||
4077 | 1684 | log("retry UpdateGoalState,retry=" + strRetry) | ||
4078 | 1685 | goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate") | ||
4079 | 1686 | if goalStateXml != None: | ||
4080 | 1687 | break | ||
4081 | 1688 | log = Log | ||
4082 | 1689 | time.sleep(retry) | ||
4083 | 1690 | if not goalStateXml: | ||
4084 | 1691 | Error("UpdateGoalState failed.") | ||
4085 | 1692 | return | ||
4086 | 1693 | Log("Retrieved GoalState from Windows Azure Fabric.") | ||
4087 | 1694 | self.GoalState = GoalState(self).Parse(goalStateXml) | ||
4088 | 1695 | return self.GoalState | ||
4089 | 1696 | |||
4090 | 1697 | def ReportReady(self): | ||
4091 | 1698 | counter = (self.HealthReportCounter + 1) % 1000000 | ||
4092 | 1699 | self.HealthReportCounter = counter | ||
4093 | 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>" | ||
4094 | 1701 | + self.GoalState.Incarnation | ||
4095 | 1702 | + "</GoalStateIncarnation><Container><ContainerId>" | ||
4096 | 1703 | + self.GoalState.ContainerId | ||
4097 | 1704 | + "</ContainerId><RoleInstanceList><Role><InstanceId>" | ||
4098 | 1705 | + self.GoalState.RoleInstanceId | ||
4099 | 1706 | + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>") | ||
4100 | 1707 | a = self.HttpPost("/machine?comp=health", healthReport) | ||
4101 | 1708 | if a != None: | ||
4102 | 1709 | return a.getheader("x-ms-latest-goal-state-incarnation-number") | ||
4103 | 1710 | return None | ||
4104 | 1711 | |||
4105 | 1712 | def ReportNotReady(self, status, desc): | ||
4106 | 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>" | ||
4107 | 1714 | + self.GoalState.Incarnation | ||
4108 | 1715 | + "</GoalStateIncarnation><Container><ContainerId>" | ||
4109 | 1716 | + self.GoalState.ContainerId | ||
4110 | 1717 | + "</ContainerId><RoleInstanceList><Role><InstanceId>" | ||
4111 | 1718 | + self.GoalState.RoleInstanceId | ||
4112 | 1719 | + "</InstanceId><Health><State>NotReady</State>" | ||
4113 | 1720 | + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>" | ||
4114 | 1721 | + "</Health></Role></RoleInstanceList></Container></Health>") | ||
4115 | 1722 | a = self.HttpPost("/machine?comp=health", healthReport) | ||
4116 | 1723 | if a != None: | ||
4117 | 1724 | return a.getheader("x-ms-latest-goal-state-incarnation-number") | ||
4118 | 1725 | return None | ||
4119 | 1726 | |||
4120 | 1727 | def ReportRoleProperties(self, thumbprint): | ||
4121 | 1728 | roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>" | ||
4122 | 1729 | + "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>" | ||
4123 | 1730 | + "<RoleInstances><RoleInstance>" | ||
4124 | 1731 | + "<Id>" + self.GoalState.RoleInstanceId + "</Id>" | ||
4125 | 1732 | + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>" | ||
4126 | 1733 | + "</RoleInstance></RoleInstances></Container></RoleProperties>") | ||
4127 | 1734 | a = self.HttpPost("/machine?comp=roleProperties", roleProperties) | ||
4128 | 1735 | Log("Posted Role Properties. CertificateThumbprint=" + thumbprint) | ||
4129 | 1736 | return a | ||
4130 | 1737 | |||
4131 | 1738 | def LoadBalancerProbeServer_Shutdown(self): | ||
4132 | 1739 | if self.LoadBalancerProbeServer != None: | ||
4133 | 1740 | self.LoadBalancerProbeServer.shutdown() | ||
4134 | 1741 | self.LoadBalancerProbeServer = None | ||
4135 | 1742 | |||
4136 | 1743 | def GenerateTransportCert(self): | ||
4137 | 1744 | Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem") | ||
4138 | 1745 | cert = "" | ||
4139 | 1746 | for line in GetFileContents("TransportCert.pem").split('\n'): | ||
4140 | 1747 | if not "CERTIFICATE" in line: | ||
4141 | 1748 | cert += line.rstrip() | ||
4142 | 1749 | return cert | ||
4143 | 1750 | |||
4144 | 1751 | def Provision(self): | ||
4145 | 1752 | if IsWindows(): | ||
4146 | 1753 | Log("Skipping Provision on Windows") | ||
4147 | 1754 | return | ||
4148 | 1755 | enabled = Config.get("Provisioning.Enabled") | ||
4149 | 1756 | if enabled != None and enabled.lower().startswith("n"): | ||
4150 | 1757 | return | ||
4151 | 1758 | Log("Provisioning image started.") | ||
4152 | 1759 | type = Config.get("Provisioning.SshHostKeyPairType") | ||
4153 | 1760 | if type == None: | ||
4154 | 1761 | type = "rsa" | ||
4155 | 1762 | regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair") | ||
4156 | 1763 | if regenerateKeys == None or regenerateKeys.lower().startswith("y"): | ||
4157 | 1764 | Run("rm -f /etc/ssh/ssh_host_*key*") | ||
4158 | 1765 | Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key") | ||
4159 | 1766 | ReloadSshd() | ||
4160 | 1767 | SetFileContents(LibDir + "/provisioned", "") | ||
4161 | 1768 | dvd = "/dev/hdc" | ||
4162 | 1769 | if os.path.exists("/dev/sr0"): | ||
4163 | 1770 | dvd = "/dev/sr0" | ||
4164 | 1771 | modloaded=False | ||
4165 | 1772 | if Run("fdisk -l " + dvd + " | grep Disk"): | ||
4166 | 1773 | # Is it possible to load a module for ata_piix? | ||
4167 | 1774 | retcode,krn=RunSafe('uname -r') | ||
4168 | 1775 | if retcode: | ||
4169 | 1776 | Error("Unable to provision: Failed to call uname -a") | ||
4170 | 1777 | return "Unable to provision: Failed to mount DVD." | ||
4171 | 1778 | krn_pth='/lib/modules/'+krn+'/kernel/drivers/ata/ata_piix.ko' | ||
4172 | 1779 | if not os.path.isfile(krn_pth): | ||
4173 | 1780 | Error("Unable to provision: Failed to locate ata_piix.ko") | ||
4174 | 1781 | return "Unable to provision: Failed to mount DVD." | ||
4175 | 1782 | retcode,output=RunSafe('insmod ' + krn_pth) | ||
4176 | 1783 | if retcode: | ||
4177 | 1784 | Error("Unable to provision: Failed to insmod " + krn+pth) | ||
4178 | 1785 | return "Failed to retrieve provisioning data (0x01)." | ||
4179 | 1786 | modloaded=True | ||
4180 | 1787 | Log("Provision: Loaded " + krn_pth + " driver for ATAPI CD-ROM") | ||
4181 | 1788 | # we have succeeded loading the ata_piix mod | ||
4182 | 1789 | for i in range(10): # we may have to wait | ||
4183 | 1790 | if os.path.exists("/dev/sr0"): | ||
4184 | 1791 | dvd = "/dev/sr0" | ||
4185 | 1792 | break | ||
4186 | 1793 | Log("Waiting for DVD - sleeping 1 - "+str(i+1)+" try...") | ||
4187 | 1794 | time.sleep(1) | ||
4188 | 1795 | CreateDir("/mnt/cdrom/secure", "root", 0700) | ||
4189 | 1796 | Run("mount " + dvd + " /mnt/cdrom/secure") | ||
4190 | 1797 | if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"): | ||
4191 | 1798 | Error("Unable to provision: Missing ovf-env.xml on DVD.") | ||
4192 | 1799 | return "Failed to retrieve provisioning data (0x02)." | ||
4193 | 1800 | ovfxml = GetFileContents("/mnt/cdrom/secure/ovf-env.xml") | ||
4194 | 1801 | SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml)) | ||
4195 | 1802 | Run("umount /mnt/cdrom/secure") | ||
4196 | 1803 | if modloaded: | ||
4197 | 1804 | Run('rmmod ' + krn_pth) | ||
4198 | 1805 | error = None | ||
4199 | 1806 | if ovfxml != None: | ||
4200 | 1807 | Log("Provisioning image using OVF settings in the DVD.") | ||
4201 | 1808 | ovfobj = OvfEnv().Parse(ovfxml) | ||
4202 | 1809 | if ovfobj != None: | ||
4203 | 1810 | error = ovfobj.Process() | ||
4204 | 1811 | if error : | ||
4205 | 1812 | Error ("Provisioninig image FAILED " + error) | ||
4206 | 1813 | return ("Provisioninig image FAILED " + error) | ||
4207 | 1814 | # This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml | ||
4208 | 1815 | fingerprint = os.popen("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub").read().rstrip().split()[1].replace(':','') | ||
4209 | 1816 | self.ReportRoleProperties(fingerprint) | ||
4210 | 1817 | delRootPass = Config.get("Provisioning.DeleteRootPassword") | ||
4211 | 1818 | if delRootPass != None and delRootPass.lower().startswith("y"): | ||
4212 | 1819 | DeleteRootPassword() | ||
4213 | 1820 | Log("Provisioning image completed.") | ||
4214 | 1821 | return error | ||
4215 | 1822 | |||
4216 | 1823 | def Run(self): | ||
4217 | 1824 | if IsLinux(): | ||
4218 | 1825 | SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n") | ||
4219 | 1826 | |||
4220 | 1827 | if GetIpv4Address() == None: | ||
4221 | 1828 | Log("Waiting for network.") | ||
4222 | 1829 | while(GetIpv4Address() == None): | ||
4223 | 1830 | time.sleep(10) | ||
4224 | 1831 | |||
4225 | 1832 | Log("IPv4 address: " + GetIpv4Address()) | ||
4226 | 1833 | Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in GetMacAddress()])) | ||
4227 | 1834 | |||
4228 | 1835 | # Consume Entropy in ACPI table provided by Hyper-V | ||
4229 | 1836 | try: | ||
4230 | 1837 | SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0")) | ||
4231 | 1838 | except: | ||
4232 | 1839 | pass | ||
4233 | 1840 | |||
4234 | 1841 | Log("Probing for Windows Azure environment.") | ||
4235 | 1842 | self.Endpoint = self.DoDhcpWork() | ||
4236 | 1843 | |||
4237 | 1844 | if self.Endpoint == None: | ||
4238 | 1845 | Log("Windows Azure environment not detected.") | ||
4239 | 1846 | while True: | ||
4240 | 1847 | time.sleep(60) | ||
4241 | 1848 | |||
4242 | 1849 | Log("Discovered Windows Azure endpoint: " + self.Endpoint) | ||
4243 | 1850 | if not self.CheckVersions(): | ||
4244 | 1851 | Error("Agent.CheckVersions failed") | ||
4245 | 1852 | sys.exit(1) | ||
4246 | 1853 | |||
4247 | 1854 | self.EnvMonitor = EnvMonitor() | ||
4248 | 1855 | |||
4249 | 1856 | # Set SCSI timeout on root device | ||
4250 | 1857 | try: | ||
4251 | 1858 | timeout = Config.get("OS.RootDeviceScsiTimeout") | ||
4252 | 1859 | if timeout != None: | ||
4253 | 1860 | SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout) | ||
4254 | 1861 | except: | ||
4255 | 1862 | pass | ||
4256 | 1863 | |||
4257 | 1864 | global Openssl | ||
4258 | 1865 | Openssl = Config.get("OS.OpensslPath") | ||
4259 | 1866 | if Openssl == None: | ||
4260 | 1867 | Openssl = "openssl" | ||
4261 | 1868 | |||
4262 | 1869 | self.TransportCert = self.GenerateTransportCert() | ||
4263 | 1870 | |||
4264 | 1871 | incarnation = None # goalStateIncarnationFromHealthReport | ||
4265 | 1872 | currentPort = None # loadBalancerProbePort | ||
4266 | 1873 | goalState = None # self.GoalState, instance of GoalState | ||
4267 | 1874 | provisioned = os.path.exists(LibDir + "/provisioned") | ||
4268 | 1875 | program = Config.get("Role.StateConsumer") | ||
4269 | 1876 | provisionError = None | ||
4270 | 1877 | lbProbeResponder = True | ||
4271 | 1878 | setting = Config.get("LBProbeResponder") | ||
4272 | 1879 | if setting != None and setting.lower().startswith("n"): | ||
4273 | 1880 | lbProbeResponder = False | ||
4274 | 1881 | while True: | ||
4275 | 1882 | if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation): | ||
4276 | 1883 | goalState = self.UpdateGoalState() | ||
4277 | 1884 | |||
4278 | 1885 | if provisioned == False: | ||
4279 | 1886 | self.ReportNotReady("Provisioning", "Starting") | ||
4280 | 1887 | |||
4281 | 1888 | goalState.Process() | ||
4282 | 1889 | |||
4283 | 1890 | if provisioned == False: | ||
4284 | 1891 | provisionError = self.Provision() | ||
4285 | 1892 | provisioned = True | ||
4286 | 1893 | |||
4287 | 1894 | # | ||
4288 | 1895 | # only one port supported | ||
4289 | 1896 | # restart server if new port is different than old port | ||
4290 | 1897 | # stop server if no longer a port | ||
4291 | 1898 | # | ||
4292 | 1899 | goalPort = goalState.LoadBalancerProbePort | ||
4293 | 1900 | if currentPort != goalPort: | ||
4294 | 1901 | self.LoadBalancerProbeServer_Shutdown() | ||
4295 | 1902 | currentPort = goalPort | ||
4296 | 1903 | if currentPort != None and lbProbeResponder == True: | ||
4297 | 1904 | self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort) | ||
4298 | 1905 | if self.LoadBalancerProbeServer == None : | ||
4299 | 1906 | lbProbeResponder = False | ||
4300 | 1907 | Log("Unable to create LBProbeResponder.") | ||
4301 | 1908 | |||
4302 | 1909 | if program != None and DiskActivated == True: | ||
4303 | 1910 | Children.append(subprocess.Popen([program, "Ready"])) | ||
4304 | 1911 | program = None | ||
4305 | 1912 | |||
4306 | 1913 | if goalState.ExpectedState == "Stopped": | ||
4307 | 1914 | program = Config.get("Role.StateConsumer") | ||
4308 | 1915 | if program != None: | ||
4309 | 1916 | Run(program + " Shutdown") | ||
4310 | 1917 | self.EnvMonitor.ShutdownService() | ||
4311 | 1918 | self.LoadBalancerProbeServer_Shutdown() | ||
4312 | 1919 | command = ["/sbin/shutdown -hP now", "shutdown /s /t 5"][IsWindows()] | ||
4313 | 1920 | Run(command) | ||
4314 | 1921 | return | ||
4315 | 1922 | |||
4316 | 1923 | sleepToReduceAccessDenied = 3 | ||
4317 | 1924 | time.sleep(sleepToReduceAccessDenied) | ||
4318 | 1925 | if provisionError != None: | ||
4319 | 1926 | incarnation = self.ReportNotReady("ProvisioningFailed", provisionError) | ||
4320 | 1927 | else: | ||
4321 | 1928 | incarnation = self.ReportReady() | ||
4322 | 1929 | time.sleep(25 - sleepToReduceAccessDenied) | ||
4323 | 1930 | |||
4324 | 1931 | Init_Suse = """\ | ||
4325 | 1932 | #! /bin/sh | ||
4326 | 1933 | |||
4327 | 1934 | ### BEGIN INIT INFO | ||
4328 | 1935 | # Provides: WindowsAzureLinuxAgent | ||
4329 | 1936 | # Required-Start: $network sshd | ||
4330 | 1937 | # Required-Stop: $network sshd | ||
4331 | 1938 | # Default-Start: 3 5 | ||
4332 | 1939 | # Default-Stop: 0 1 2 6 | ||
4333 | 1940 | # Description: Start the WindowsAzureLinuxAgent | ||
4334 | 1941 | ### END INIT INFO | ||
4335 | 1942 | |||
4336 | 1943 | WAZD_BIN=/usr/sbin/waagent | ||
4337 | 1944 | test -x $WAZD_BIN || exit 5 | ||
4338 | 1945 | |||
4339 | 1946 | case "$1" in | ||
4340 | 1947 | start) | ||
4341 | 1948 | echo "Starting WindowsAzureLinuxAgent" | ||
4342 | 1949 | ## Start daemon with startproc(8). If this fails | ||
4343 | 1950 | ## the echo return value is set appropriate. | ||
4344 | 1951 | |||
4345 | 1952 | startproc -f $WAZD_BIN -daemon | ||
4346 | 1953 | exit $? | ||
4347 | 1954 | ;; | ||
4348 | 1955 | stop) | ||
4349 | 1956 | echo "Shutting down WindowsAzureLinuxAgent" | ||
4350 | 1957 | ## Stop daemon with killproc(8) and if this fails | ||
4351 | 1958 | ## set echo the echo return value. | ||
4352 | 1959 | |||
4353 | 1960 | killproc -p /var/run/waagent.pid $WAZD_BIN | ||
4354 | 1961 | exit $? | ||
4355 | 1962 | ;; | ||
4356 | 1963 | try-restart) | ||
4357 | 1964 | ## Stop the service and if this succeeds (i.e. the | ||
4358 | 1965 | ## service was running before), start it again. | ||
4359 | 1966 | $0 status >/dev/null && $0 restart | ||
4360 | 1967 | ;; | ||
4361 | 1968 | restart) | ||
4362 | 1969 | ## Stop the service and regardless of whether it was | ||
4363 | 1970 | ## running or not, start it again. | ||
4364 | 1971 | $0 stop | ||
4365 | 1972 | $0 start | ||
4366 | 1973 | ;; | ||
4367 | 1974 | force-reload|reload) | ||
4368 | 1975 | ;; | ||
4369 | 1976 | status) | ||
4370 | 1977 | echo -n "Checking for service WindowsAzureLinuxAgent " | ||
4371 | 1978 | ## Check status with checkproc(8), if process is running | ||
4372 | 1979 | ## checkproc will return with exit status 0. | ||
4373 | 1980 | |||
4374 | 1981 | checkproc -p $WAZD_PIDFILE $WAZD_BIN | ||
4375 | 1982 | exit $? | ||
4376 | 1983 | ;; | ||
4377 | 1984 | probe) | ||
4378 | 1985 | ;; | ||
4379 | 1986 | *) | ||
4380 | 1987 | echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}" | ||
4381 | 1988 | exit 1 | ||
4382 | 1989 | ;; | ||
4383 | 1990 | esac | ||
4384 | 1991 | """ | ||
4385 | 1992 | |||
4386 | 1993 | Init_RedHat = """\ | ||
4387 | 1994 | #!/bin/bash | ||
4388 | 1995 | # | ||
4389 | 1996 | # Init file for WindowsAzureLinuxAgent. | ||
4390 | 1997 | # | ||
4391 | 1998 | # chkconfig: 2345 60 80 | ||
4392 | 1999 | # description: WindowsAzureLinuxAgent | ||
4393 | 2000 | # | ||
4394 | 2001 | |||
4395 | 2002 | # source function library | ||
4396 | 2003 | . /etc/rc.d/init.d/functions | ||
4397 | 2004 | |||
4398 | 2005 | RETVAL=0 | ||
4399 | 2006 | FriendlyName="WindowsAzureLinuxAgent" | ||
4400 | 2007 | WAZD_BIN=/usr/sbin/waagent | ||
4401 | 2008 | |||
4402 | 2009 | start() | ||
4403 | 2010 | { | ||
4404 | 2011 | echo -n $"Starting $FriendlyName: " | ||
4405 | 2012 | $WAZD_BIN -daemon & | ||
4406 | 2013 | } | ||
4407 | 2014 | |||
4408 | 2015 | stop() | ||
4409 | 2016 | { | ||
4410 | 2017 | echo -n $"Stopping $FriendlyName: " | ||
4411 | 2018 | killproc -p /var/run/waagent.pid $WAZD_BIN | ||
4412 | 2019 | RETVAL=$? | ||
4413 | 2020 | echo | ||
4414 | 2021 | return $RETVAL | ||
4415 | 2022 | } | ||
4416 | 2023 | |||
4417 | 2024 | case "$1" in | ||
4418 | 2025 | start) | ||
4419 | 2026 | start | ||
4420 | 2027 | ;; | ||
4421 | 2028 | stop) | ||
4422 | 2029 | stop | ||
4423 | 2030 | ;; | ||
4424 | 2031 | restart) | ||
4425 | 2032 | stop | ||
4426 | 2033 | start | ||
4427 | 2034 | ;; | ||
4428 | 2035 | reload) | ||
4429 | 2036 | ;; | ||
4430 | 2037 | report) | ||
4431 | 2038 | ;; | ||
4432 | 2039 | status) | ||
4433 | 2040 | status $WAZD_BIN | ||
4434 | 2041 | RETVAL=$? | ||
4435 | 2042 | ;; | ||
4436 | 2043 | *) | ||
4437 | 2044 | echo $"Usage: $0 {start|stop|restart|status}" | ||
4438 | 2045 | RETVAL=1 | ||
4439 | 2046 | esac | ||
4440 | 2047 | exit $RETVAL | ||
4441 | 2048 | """ | ||
4442 | 2049 | |||
4443 | 2050 | Init_Ubuntu = """\ | ||
4444 | 2051 | #walinuxagent - start Windows Azure agent | ||
4445 | 2052 | |||
4446 | 2053 | description "walinuxagent" | ||
4447 | 2054 | author "Ben Howard <ben.howard@canonical.com>" | ||
4448 | 2055 | |||
4449 | 2056 | start on (filesystem and started rsyslog) | ||
4450 | 2057 | |||
4451 | 2058 | pre-start script | ||
4452 | 2059 | |||
4453 | 2060 | WALINUXAGENT_ENABLED=1 | ||
4454 | 2061 | [ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent | ||
4455 | 2062 | |||
4456 | 2063 | if [ "$WALINUXAGENT_ENABLED" != "1" ]; then | ||
4457 | 2064 | exit 1 | ||
4458 | 2065 | fi | ||
4459 | 2066 | |||
4460 | 2067 | if [ ! -x /usr/sbin/waagent ]; then | ||
4461 | 2068 | exit 1 | ||
4462 | 2069 | fi | ||
4463 | 2070 | |||
4464 | 2071 | #Load the udf module | ||
4465 | 2072 | modprobe -b udf | ||
4466 | 2073 | end script | ||
4467 | 2074 | |||
4468 | 2075 | exec /usr/sbin/waagent -daemon | ||
4469 | 2076 | """ | ||
4470 | 2077 | |||
4471 | 2078 | Init_Debian = """\ | ||
4472 | 2079 | #!/bin/sh | ||
4473 | 2080 | ### BEGIN INIT INFO | ||
4474 | 2081 | # Provides: WindowsAzureLinuxAgent | ||
4475 | 2082 | # Required-Start: $network $syslog | ||
4476 | 2083 | # Required-Stop: $network $syslog | ||
4477 | 2084 | # Should-Start: $network $syslog | ||
4478 | 2085 | # Should-Stop: $network $syslog | ||
4479 | 2086 | # Default-Start: 2 3 4 5 | ||
4480 | 2087 | # Default-Stop: 0 1 6 | ||
4481 | 2088 | # Short-Description: WindowsAzureLinuxAgent | ||
4482 | 2089 | # Description: WindowsAzureLinuxAgent | ||
4483 | 2090 | ### END INIT INFO | ||
4484 | 2091 | |||
4485 | 2092 | . /lib/lsb/init-functions | ||
4486 | 2093 | |||
4487 | 2094 | OPTIONS="-daemon" | ||
4488 | 2095 | WAZD_BIN=/usr/sbin/waagent | ||
4489 | 2096 | WAZD_PID=/var/run/waagent.pid | ||
4490 | 2097 | |||
4491 | 2098 | case "$1" in | ||
4492 | 2099 | start) | ||
4493 | 2100 | log_begin_msg "Starting WindowsAzureLinuxAgent..." | ||
4494 | 2101 | pid=$( pidofproc $WAZD_BIN ) | ||
4495 | 2102 | if [ -n "$pid" ] ; then | ||
4496 | 2103 | log_begin_msg "Already running." | ||
4497 | 2104 | log_end_msg 0 | ||
4498 | 2105 | exit 0 | ||
4499 | 2106 | fi | ||
4500 | 2107 | start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS | ||
4501 | 2108 | log_end_msg $? | ||
4502 | 2109 | ;; | ||
4503 | 2110 | |||
4504 | 2111 | stop) | ||
4505 | 2112 | log_begin_msg "Stopping WindowsAzureLinuxAgent..." | ||
4506 | 2113 | start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID | ||
4507 | 2114 | ret=$? | ||
4508 | 2115 | rm -f $WAZD_PID | ||
4509 | 2116 | log_end_msg $ret | ||
4510 | 2117 | ;; | ||
4511 | 2118 | force-reload) | ||
4512 | 2119 | $0 restart | ||
4513 | 2120 | ;; | ||
4514 | 2121 | restart) | ||
4515 | 2122 | $0 stop | ||
4516 | 2123 | $0 start | ||
4517 | 2124 | ;; | ||
4518 | 2125 | status) | ||
4519 | 2126 | status_of_proc $WAZD_BIN && exit 0 || exit $? | ||
4520 | 2127 | ;; | ||
4521 | 2128 | *) | ||
4522 | 2129 | log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}" | ||
4523 | 2130 | exit 1 | ||
4524 | 2131 | ;; | ||
4525 | 2132 | esac | ||
4526 | 2133 | |||
4527 | 2134 | exit 0 | ||
4528 | 2135 | """ | ||
4529 | 2136 | |||
4530 | 2137 | WaagentConf = """\ | ||
4531 | 2138 | # | ||
4532 | 2139 | # Windows Azure Linux Agent Configuration | ||
4533 | 2140 | # | ||
4534 | 2141 | |||
4535 | 2142 | Role.StateConsumer=None # Specified program is invoked with "Ready" or "Shutdown". | ||
4536 | 2143 | # Shutdown will be initiated only after the program returns. Windows Azure will | ||
4537 | 2144 | # power off the VM if shutdown is not completed within ?? minutes. | ||
4538 | 2145 | Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration. | ||
4539 | 2146 | Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology. | ||
4540 | 2147 | |||
4541 | 2148 | Provisioning.Enabled=y # | ||
4542 | 2149 | Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable. | ||
4543 | 2150 | Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair. | ||
4544 | 2151 | Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa". | ||
4545 | 2152 | Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests. | ||
4546 | 2153 | |||
4547 | 2154 | ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted. | ||
4548 | 2155 | ResourceDisk.Filesystem=ext4 # | ||
4549 | 2156 | ResourceDisk.MountPoint=/mnt/resource # | ||
4550 | 2157 | ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk. | ||
4551 | 2158 | ResourceDisk.SwapSizeMB=0 # Size of the swapfile. | ||
4552 | 2159 | |||
4553 | 2160 | LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure. | ||
4554 | 2161 | |||
4555 | 2162 | Logs.Verbose=n # | ||
4556 | 2163 | |||
4557 | 2164 | OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds. | ||
4558 | 2165 | OS.OpensslPath=None # If "None", the system default version is used. | ||
4559 | 2166 | """ | ||
4560 | 2167 | |||
4561 | 2168 | WaagentLogrotate = """\ | ||
4562 | 2169 | /var/log/waagent.log { | ||
4563 | 2170 | monthly | ||
4564 | 2171 | rotate 6 | ||
4565 | 2172 | notifempty | ||
4566 | 2173 | missingok | ||
4567 | 2174 | } | ||
4568 | 2175 | """ | ||
4569 | 2176 | |||
4570 | 2177 | def AddToLinuxKernelCmdline(options): | ||
4571 | 2178 | if os.path.isfile("/boot/grub/menu.lst"): | ||
4572 | 2179 | Run("sed -i --follow-symlinks '/kernel/s|$| " + options + " |' /boot/grub/menu.lst") | ||
4573 | 2180 | filepath = "/etc/default/grub" | ||
4574 | 2181 | if os.path.isfile(filepath): | ||
4575 | 2182 | filecontents = GetFileContents(filepath).split('\n') | ||
4576 | 2183 | current = filter(lambda a: a.startswith("GRUB_CMDLINE_LINUX"), filecontents) | ||
4577 | 2184 | ReplaceFileContentsAtomic(filepath, | ||
4578 | 2185 | "\n".join(filter(lambda a: not a.startswith("GRUB_CMDLINE_LINUX"), filecontents)) | ||
4579 | 2186 | + current[0][:-1] + " " + options + "\"\n") | ||
4580 | 2187 | Run("update-grub") | ||
4581 | 2188 | |||
4582 | 2189 | def ApplyVNUMAWorkaround(): | ||
4583 | 2190 | VersionParts = platform.release().replace('-', '.').split('.') | ||
4584 | 2191 | if int(VersionParts[0]) > 2: | ||
4585 | 2192 | return | ||
4586 | 2193 | if int(VersionParts[1]) > 6: | ||
4587 | 2194 | return | ||
4588 | 2195 | if int(VersionParts[2]) > 37: | ||
4589 | 2196 | return | ||
4590 | 2197 | AddToLinuxKernelCmdline("numa=off") | ||
4591 | 2198 | # TODO: This is not ideal for offline installation. | ||
4592 | 2199 | print("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.") | ||
4593 | 2200 | |||
4594 | 2201 | def RevertVNUMAWorkaround(): | ||
4595 | 2202 | print("Automatic reverting of GRUB configuration is not yet supported. Please edit by hand.") | ||
4596 | 2203 | |||
4597 | 2204 | def Install(): | ||
4598 | 2205 | if IsWindows(): | ||
4599 | 2206 | print("ERROR: -install invalid for Windows.") | ||
4600 | 2207 | return 1 | ||
4601 | 2208 | os.chmod(sys.argv[0], 0755) | ||
4602 | 2209 | SwitchCwd() | ||
4603 | 2210 | requiredDeps = [ "/sbin/route", "/sbin/shutdown" ] | ||
4604 | 2211 | if IsDebian(): | ||
4605 | 2212 | requiredDeps += [ "/usr/sbin/update-rc.d" ] | ||
4606 | 2213 | if IsSuse(): | ||
4607 | 2214 | requiredDeps += [ "/sbin/insserv" ] | ||
4608 | 2215 | for a in requiredDeps: | ||
4609 | 2216 | if not os.path.isfile(a): | ||
4610 | 2217 | Error("Missing required dependency: " + a) | ||
4611 | 2218 | return 1 | ||
4612 | 2219 | missing = False | ||
4613 | 2220 | for a in [ "ssh-keygen", "useradd", "openssl", "sfdisk", | ||
4614 | 2221 | "fdisk", "mkfs", "chpasswd", "sed", "grep", "sudo" ]: | ||
4615 | 2222 | if Run("which " + a + " > /dev/null 2>&1"): | ||
4616 | 2223 | Warn("Missing dependency: " + a) | ||
4617 | 2224 | missing = True | ||
4618 | 2225 | if missing == True: | ||
4619 | 2226 | Warn("Please resolve missing dependencies listed for full functionality.") | ||
4620 | 2227 | if UsesRpm(): | ||
4621 | 2228 | if not Run("rpm --quiet -q NetworkManager"): | ||
4622 | 2229 | Error(GuestAgentLongName + " is not compatible with NetworkManager.") | ||
4623 | 2230 | return 1 | ||
4624 | 2231 | if Run("rpm --quiet -q python-pyasn1"): | ||
4625 | 2232 | Error(GuestAgentLongName + " requires python-pyasn1.") | ||
4626 | 2233 | return 1 | ||
4627 | 2234 | if UsesDpkg() and not Run("dpkg-query -s network-manager >/dev/null 2>&1"): | ||
4628 | 2235 | Error(GuestAgentLongName + " is not compatible with network-manager.") | ||
4629 | 2236 | return 1 | ||
4630 | 2237 | for a in RulesFiles: | ||
4631 | 2238 | if os.path.isfile(a): | ||
4632 | 2239 | if os.path.isfile(GetLastPathElement(a)): | ||
4633 | 2240 | os.remove(GetLastPathElement(a)) | ||
4634 | 2241 | shutil.move(a, ".") | ||
4635 | 2242 | Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) ) | ||
4636 | 2243 | |||
4637 | 2244 | if IsUbuntu(): | ||
4638 | 2245 | # Support for Ubuntu's upstart configuration | ||
4639 | 2246 | filename="waagent.conf" | ||
4640 | 2247 | filepath = "/etc/init/" + filename | ||
4641 | 2248 | SetFileContents(filepath, Init_Ubuntu) | ||
4642 | 2249 | os.chmod(filepath, 0644) | ||
4643 | 2250 | |||
4644 | 2251 | else: | ||
4645 | 2252 | # Regular init.d configurations | ||
4646 | 2253 | filename = "waagent" | ||
4647 | 2254 | filepath = "/etc/init.d/" + filename | ||
4648 | 2255 | distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3 | ||
4649 | 2256 | if distro == 0: | ||
4650 | 2257 | Error("Unable to detect Linux Distribution.") | ||
4651 | 2258 | return 1 | ||
4652 | 2259 | init = [[Init_RedHat, "chkconfig --add " + filename], | ||
4653 | 2260 | [Init_Debian, "update-rc.d " + filename + " defaults"], | ||
4654 | 2261 | [Init_Suse, "insserv " + filename]][distro - 1] | ||
4655 | 2262 | SetFileContents(filepath, init[0]) | ||
4656 | 2263 | os.chmod(filepath, 0755) | ||
4657 | 2264 | Run(init[1]) | ||
4658 | 2265 | if os.path.isfile("/etc/waagent.conf"): | ||
4659 | 2266 | try: | ||
4660 | 2267 | os.remove("/etc/waagent.conf.old") | ||
4661 | 2268 | except: | ||
4662 | 2269 | pass | ||
4663 | 2270 | try: | ||
4664 | 2271 | os.rename("/etc/waagent.conf", "/etc/waagent.conf.old") | ||
4665 | 2272 | Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old") | ||
4666 | 2273 | except: | ||
4667 | 2274 | pass | ||
4668 | 2275 | SetFileContents("/etc/waagent.conf", WaagentConf) | ||
4669 | 2276 | SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate) | ||
4670 | 2277 | filepath = "/etc/ssh/sshd_config" | ||
4671 | 2278 | ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not | ||
4672 | 2279 | a.startswith("ClientAliveInterval"), | ||
4673 | 2280 | GetFileContents(filepath).split('\n'))) + "ClientAliveInterval 180\n") | ||
4674 | 2281 | Log("Configured SSH client probing to keep connections alive.") | ||
4675 | 2282 | ApplyVNUMAWorkaround() | ||
4676 | 2283 | return 0 | ||
4677 | 2284 | |||
4678 | 2285 | def Uninstall(): | ||
4679 | 2286 | if IsWindows(): | ||
4680 | 2287 | print("ERROR: -uninstall invalid for windows, see waagent_service.exe") | ||
4681 | 2288 | return 1 | ||
4682 | 2289 | SwitchCwd() | ||
4683 | 2290 | for a in RulesFiles: | ||
4684 | 2291 | if os.path.isfile(GetLastPathElement(a)): | ||
4685 | 2292 | try: | ||
4686 | 2293 | shutil.move(GetLastPathElement(a), a) | ||
4687 | 2294 | Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a ) | ||
4688 | 2295 | except: | ||
4689 | 2296 | pass | ||
4690 | 2297 | filename = "waagent" | ||
4691 | 2298 | a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3 | ||
4692 | 2299 | if a == 0: | ||
4693 | 2300 | Error("Unable to detect Linux Distribution.") | ||
4694 | 2301 | return 1 | ||
4695 | 2302 | Run("service " + filename + " stop") | ||
4696 | 2303 | cmd = ["chkconfig --del " + filename, | ||
4697 | 2304 | "update-rc.d -f " + filename + " remove", | ||
4698 | 2305 | "insserv -r " + filename][a - 1] | ||
4699 | 2306 | Run(cmd) | ||
4700 | 2307 | for f in os.listdir(LibDir) + ["/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]: | ||
4701 | 2308 | try: | ||
4702 | 2309 | os.remove(f) | ||
4703 | 2310 | except: | ||
4704 | 2311 | pass | ||
4705 | 2312 | RevertVNUMAWorkaround() | ||
4706 | 2313 | return 0 | ||
4707 | 2314 | |||
4708 | 2315 | def DeleteRootPassword(): | ||
4709 | 2316 | filepath="/etc/shadow" | ||
4710 | 2317 | ReplaceFileContentsAtomic(filepath, "root:*LOCK*:14600::::::\n" + "\n".join(filter(lambda a: not | ||
4711 | 2318 | a.startswith("root:"), | ||
4712 | 2319 | GetFileContents(filepath).split('\n')))) | ||
4713 | 2320 | os.chmod(filepath, 0000) | ||
4714 | 2321 | if IsRedHat(): | ||
4715 | 2322 | Run("chcon system_u:object_r:shadow_t:s0 " + filepath) | ||
4716 | 2323 | Log("Root password deleted.") | ||
4717 | 2324 | |||
4718 | 2325 | def Deprovision(force, deluser): | ||
4719 | 2326 | if IsWindows(): | ||
4720 | 2327 | Run(os.environ["windir"] + "\\system32\\sysprep\\sysprep.exe /generalize") | ||
4721 | 2328 | return 0 | ||
4722 | 2329 | |||
4723 | 2330 | SwitchCwd() | ||
4724 | 2331 | ovfxml = GetFileContents("ovf-env.xml") | ||
4725 | 2332 | ovfobj = None | ||
4726 | 2333 | if ovfxml != None: | ||
4727 | 2334 | ovfobj = OvfEnv().Parse(ovfxml) | ||
4728 | 2335 | |||
4729 | 2336 | print("WARNING! The waagent service will be stopped.") | ||
4730 | 2337 | print("WARNING! All SSH host key pairs will be deleted.") | ||
4731 | 2338 | if IsUbuntu(): | ||
4732 | 2339 | print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.") | ||
4733 | 2340 | else: | ||
4734 | 2341 | print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.") | ||
4735 | 2342 | print("WARNING! Cached DHCP leases will be deleted.") | ||
4736 | 2343 | |||
4737 | 2344 | delRootPass = Config.get("Provisioning.DeleteRootPassword") | ||
4738 | 2345 | if delRootPass != None and delRootPass.lower().startswith("y"): | ||
4739 | 2346 | print("WARNING! root password will be disabled. You will not be able to login as root.") | ||
4740 | 2347 | |||
4741 | 2348 | if ovfobj != None and deluser == True: | ||
4742 | 2349 | print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.") | ||
4743 | 2350 | |||
4744 | 2351 | if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'): | ||
4745 | 2352 | return 1 | ||
4746 | 2353 | |||
4747 | 2354 | Run("service waagent stop") | ||
4748 | 2355 | |||
4749 | 2356 | if deluser == True: | ||
4750 | 2357 | DeleteAccount(ovfobj.UserName) | ||
4751 | 2358 | |||
4752 | 2359 | # Remove SSH host keys | ||
4753 | 2360 | regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair") | ||
4754 | 2361 | if regenerateKeys == None or regenerateKeys.lower().startswith("y"): | ||
4755 | 2362 | Run("rm -f /etc/ssh/ssh_host_*key*") | ||
4756 | 2363 | |||
4757 | 2364 | # Remove root password | ||
4758 | 2365 | if delRootPass != None and delRootPass.lower().startswith("y"): | ||
4759 | 2366 | DeleteRootPassword() | ||
4760 | 2367 | |||
4761 | 2368 | # Remove distribution specific networking configuration | ||
4762 | 2369 | |||
4763 | 2370 | UpdateAndPublishHostNameCommon("localhost.localdomain") | ||
4764 | 2371 | |||
4765 | 2372 | # RedHat, Suse, Debian | ||
4766 | 2373 | for a in VarLibDhcpDirectories: | ||
4767 | 2374 | Run("rm -f " + a + "/*") | ||
4768 | 2375 | |||
4769 | 2376 | # Clear LibDir, remove nameserver and root bash history | ||
4770 | 2377 | fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log" ] | ||
4771 | 2378 | |||
4772 | 2379 | if IsUbuntu(): | ||
4773 | 2380 | # Ubuntu uses resolv.conf by default, so removing /etc/resolv.conf will | ||
4774 | 2381 | # break resolvconf. Therefore, we check to see if resolvconf is in use, | ||
4775 | 2382 | # and if so, we remove the resolvconf artifacts. | ||
4776 | 2383 | |||
4777 | 2384 | Log("Deprovision: Ubuntu specific resolv.conf behavior selected.") | ||
4778 | 2385 | if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf': | ||
4779 | 2386 | Log("resolvconf is not configured. Removing /etc/resolv.conf") | ||
4780 | 2387 | fileBlackList.append('/etc/resolv.conf') | ||
4781 | 2388 | else: | ||
4782 | 2389 | Log("resolvconf is enabled; leaving /etc/resolv.conf intact") | ||
4783 | 2390 | resolvConfD = '/etc/resolvconf/resolv.conf.d/' | ||
4784 | 2391 | fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'originial' ]) | ||
4785 | 2392 | else: | ||
4786 | 2393 | fileBlackList.extend(os.listdir(LibDir) + ['/etc/resolv.conf']) | ||
4787 | 2394 | |||
4788 | 2395 | for f in os.listdir(LibDir) + fileBlackList: | ||
4789 | 2396 | try: | ||
4790 | 2397 | os.remove(f) | ||
4791 | 2398 | except: | ||
4792 | 2399 | pass | ||
4793 | 2400 | return 0 | ||
4794 | 2401 | |||
4795 | 2402 | def SwitchCwd(): | ||
4796 | 2403 | if not IsWindows(): | ||
4797 | 2404 | CreateDir(LibDir, "root", 0700) | ||
4798 | 2405 | os.chdir(LibDir) | ||
4799 | 2406 | |||
4800 | 2407 | def Usage(): | ||
4801 | 2408 | print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]") | ||
4802 | 2409 | return 0 | ||
4803 | 2410 | |||
4804 | 2411 | if GuestAgentVersion == "": | ||
4805 | 2412 | print("WARNING! This is a non-standard agent that does not include a valid version string.") | ||
4806 | 2413 | if IsLinux() and not DetectLinuxDistro(): | ||
4807 | 2414 | print("WARNING! Unable to detect Linux distribution. Some functionality may be broken.") | ||
4808 | 2415 | |||
4809 | 2416 | if len(sys.argv) == 1: | ||
4810 | 2417 | sys.exit(Usage()) | ||
4811 | 2418 | |||
4812 | 2419 | args = [] | ||
4813 | 2420 | force = False | ||
4814 | 2421 | for a in sys.argv[1:]: | ||
4815 | 2422 | if re.match("^([-/]*)(help|usage|\?)", a): | ||
4816 | 2423 | sys.exit(Usage()) | ||
4817 | 2424 | elif re.match("^([-/]*)verbose", a): | ||
4818 | 2425 | Verbose = True | ||
4819 | 2426 | elif re.match("^([-/]*)force", a): | ||
4820 | 2427 | force = True | ||
4821 | 2428 | elif re.match("^([-/]*)(setup|install)", a): | ||
4822 | 2429 | sys.exit(Install()) | ||
4823 | 2430 | elif re.match("^([-/]*)(uninstall)", a): | ||
4824 | 2431 | sys.exit(Uninstall()) | ||
4825 | 2432 | else: | ||
4826 | 2433 | args.append(a) | ||
4827 | 2434 | |||
4828 | 2435 | Config = ConfigurationProvider() | ||
4829 | 2436 | |||
4830 | 2437 | verbose = Config.get("Logs.Verbose") | ||
4831 | 2438 | if verbose != None and verbose.lower().startswith("y"): | ||
4832 | 2439 | Verbose = True | ||
4833 | 2440 | |||
4834 | 2441 | daemon = False | ||
4835 | 2442 | for a in args: | ||
4836 | 2443 | if re.match("^([-/]*)deprovision\+user", a): | ||
4837 | 2444 | sys.exit(Deprovision(force, True)) | ||
4838 | 2445 | elif re.match("^([-/]*)deprovision", a): | ||
4839 | 2446 | sys.exit(Deprovision(force, False)) | ||
4840 | 2447 | elif re.match("^([-/]*)daemon", a): | ||
4841 | 2448 | daemon = True | ||
4842 | 2449 | elif re.match("^([-/]*)version", a): | ||
4843 | 2450 | print(GuestAgentVersion + " running on " + LinuxDistro) | ||
4844 | 2451 | sys.exit(0) | ||
4845 | 2452 | elif re.match("^([-/]*)serialconsole", a): | ||
4846 | 2453 | AddToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0") | ||
4847 | 2454 | Log("Configured kernel to use ttyS0 as the boot console.") | ||
4848 | 2455 | sys.exit(0) | ||
4849 | 2456 | else: | ||
4850 | 2457 | print("Invalid command line parameter:" + a) | ||
4851 | 2458 | sys.exit(1) | ||
4852 | 2459 | |||
4853 | 2460 | if daemon == False: | ||
4854 | 2461 | sys.exit(Usage()) | ||
4855 | 2462 | |||
4856 | 2463 | try: | ||
4857 | 2464 | SwitchCwd() | ||
4858 | 2465 | Log(GuestAgentLongName + " Version: " + GuestAgentVersion) | ||
4859 | 2466 | if IsLinux(): | ||
4860 | 2467 | Log("Linux Distribution Detected : " + LinuxDistro) | ||
4861 | 2468 | WaAgent = Agent() | ||
4862 | 2469 | WaAgent.Run() | ||
4863 | 2470 | except Exception, e: | ||
4864 | 2471 | Error(traceback.format_exc()) | ||
4865 | 2472 | Error("Exception: " + str(e)) | ||
4866 | 2473 | sys.exit(1) | ||
4867 | 0 | 2474 | ||
4868 | === removed directory '.pc/001-strip-init-d.patch' | |||
4869 | === removed file '.pc/001-strip-init-d.patch/waagent' | |||
4870 | --- .pc/001-strip-init-d.patch/waagent 2012-11-14 10:59:37 +0000 | |||
4871 | +++ .pc/001-strip-init-d.patch/waagent 1970-01-01 00:00:00 +0000 | |||
4872 | @@ -1,2363 +0,0 @@ | |||
4873 | 1 | #!/usr/bin/python | ||
4874 | 2 | # | ||
4875 | 3 | # Windows Azure Linux Agent | ||
4876 | 4 | # | ||
4877 | 5 | # Copyright 2012 Microsoft Corporation | ||
4878 | 6 | # | ||
4879 | 7 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
4880 | 8 | # you may not use this file except in compliance with the License. | ||
4881 | 9 | # You may obtain a copy of the License at | ||
4882 | 10 | # | ||
4883 | 11 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
4884 | 12 | # | ||
4885 | 13 | # Unless required by applicable law or agreed to in writing, software | ||
4886 | 14 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
4887 | 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
4888 | 16 | # See the License for the specific language governing permissions and | ||
4889 | 17 | # limitations under the License. | ||
4890 | 18 | # | ||
4891 | 19 | # Requires Python 2.4+ and Openssl 1.0+ | ||
4892 | 20 | # | ||
4893 | 21 | # Implements parts of RFC 2131, 1541, 1497 and | ||
4894 | 22 | # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx | ||
4895 | 23 | # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx | ||
4896 | 24 | # | ||
4897 | 25 | |||
4898 | 26 | import array | ||
4899 | 27 | import base64 | ||
4900 | 28 | import httplib | ||
4901 | 29 | import os | ||
4902 | 30 | import os.path | ||
4903 | 31 | import platform | ||
4904 | 32 | import pwd | ||
4905 | 33 | import re | ||
4906 | 34 | import shutil | ||
4907 | 35 | import socket | ||
4908 | 36 | import SocketServer | ||
4909 | 37 | import struct | ||
4910 | 38 | import subprocess | ||
4911 | 39 | import sys | ||
4912 | 40 | import tempfile | ||
4913 | 41 | import textwrap | ||
4914 | 42 | import threading | ||
4915 | 43 | import time | ||
4916 | 44 | import traceback | ||
4917 | 45 | import xml.dom.minidom | ||
4918 | 46 | |||
4919 | 47 | GuestAgentName = "WALinuxAgent" | ||
4920 | 48 | GuestAgentLongName = "Windows Azure Linux Agent" | ||
4921 | 49 | GuestAgentVersion = "WALinuxAgent-1.1" | ||
4922 | 50 | ProtocolVersion = "2011-12-31" | ||
4923 | 51 | |||
4924 | 52 | Config = None | ||
4925 | 53 | LinuxDistro = "UNKNOWN" | ||
4926 | 54 | Verbose = False | ||
4927 | 55 | WaAgent = None | ||
4928 | 56 | DiskActivated = False | ||
4929 | 57 | Openssl = "openssl" | ||
4930 | 58 | Children = [] | ||
4931 | 59 | |||
4932 | 60 | PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"] | ||
4933 | 61 | RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules", | ||
4934 | 62 | "/etc/udev/rules.d/70-persistent-net.rules" ] | ||
4935 | 63 | VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"] | ||
4936 | 64 | EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"] | ||
4937 | 65 | LibDir = "/var/lib/waagent" | ||
4938 | 66 | |||
4939 | 67 | # This lets us index into a string or an array of integers transparently. | ||
4940 | 68 | def Ord(a): | ||
4941 | 69 | if type(a) == type("a"): | ||
4942 | 70 | a = ord(a) | ||
4943 | 71 | return a | ||
4944 | 72 | |||
4945 | 73 | def IsWindows(): | ||
4946 | 74 | return (platform.uname()[0] == "Windows") | ||
4947 | 75 | |||
4948 | 76 | def IsLinux(): | ||
4949 | 77 | return (platform.uname()[0] == "Linux") | ||
4950 | 78 | |||
4951 | 79 | def DetectLinuxDistro(): | ||
4952 | 80 | global LinuxDistro | ||
4953 | 81 | if os.path.isfile("/etc/redhat-release"): | ||
4954 | 82 | LinuxDistro = "RedHat" | ||
4955 | 83 | return True | ||
4956 | 84 | if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"): | ||
4957 | 85 | LinuxDistro = "Ubuntu" | ||
4958 | 86 | return True | ||
4959 | 87 | if os.path.isfile("/etc/debian_version"): | ||
4960 | 88 | LinuxDistro = "Debian" | ||
4961 | 89 | return True | ||
4962 | 90 | if os.path.isfile("/etc/SuSE-release"): | ||
4963 | 91 | LinuxDistro = "Suse" | ||
4964 | 92 | return True | ||
4965 | 93 | return False | ||
4966 | 94 | |||
4967 | 95 | def IsRedHat(): | ||
4968 | 96 | return "RedHat" in LinuxDistro | ||
4969 | 97 | |||
4970 | 98 | def IsUbuntu(): | ||
4971 | 99 | return "Ubuntu" in LinuxDistro | ||
4972 | 100 | |||
4973 | 101 | def IsDebian(): | ||
4974 | 102 | return IsUbuntu() or "Debian" in LinuxDistro | ||
4975 | 103 | |||
4976 | 104 | def IsSuse(): | ||
4977 | 105 | return "Suse" in LinuxDistro | ||
4978 | 106 | |||
4979 | 107 | def UsesRpm(): | ||
4980 | 108 | return IsRedHat() or IsSuse() | ||
4981 | 109 | |||
4982 | 110 | def UsesDpkg(): | ||
4983 | 111 | return IsDebian() | ||
4984 | 112 | |||
4985 | 113 | def GetLastPathElement(path): | ||
4986 | 114 | return path.rsplit('/', 1)[1] | ||
4987 | 115 | |||
4988 | 116 | def GetFileContents(filepath): | ||
4989 | 117 | file = None | ||
4990 | 118 | try: | ||
4991 | 119 | file = open(filepath) | ||
4992 | 120 | except: | ||
4993 | 121 | return None | ||
4994 | 122 | if file == None: | ||
4995 | 123 | return None | ||
4996 | 124 | try: | ||
4997 | 125 | return file.read() | ||
4998 | 126 | finally: | ||
4999 | 127 | file.close() | ||
5000 | 128 |
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