summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Howard <ben.howard@ubuntu.com>2012-06-22 09:10:22 -0600
committerusd-importer <ubuntu-server@lists.ubuntu.com>2012-07-13 13:33:37 +0000
commit342dba6f6102a286979361c86b89f8c65496c8f5 (patch)
tree03e6ed63f58dae0671c779cfda0edce1c865b33b
downloadvyos-walinuxagent-342dba6f6102a286979361c86b89f8c65496c8f5.tar.gz
vyos-walinuxagent-342dba6f6102a286979361c86b89f8c65496c8f5.zip
Import patches-unapplied version 1.0~git20120606.c16f5e9-0ubuntu1 to ubuntu/quantal
Imported using git-ubuntu import.
-rw-r--r--LICENSE-2.0.txt202
-rw-r--r--NOTICE5
-rw-r--r--README352
-rw-r--r--debian/README.source7
-rw-r--r--debian/changelog9
-rw-r--r--debian/compat1
-rw-r--r--debian/control25
-rw-r--r--debian/copyright21
-rw-r--r--debian/default2
-rw-r--r--debian/dirs1
-rw-r--r--debian/docs3
-rw-r--r--debian/install1
-rw-r--r--debian/patches/000_ubuntu_init_resolvconf.patch153
-rw-r--r--debian/patches/001_ubuntu_agent_startup.patch22
-rw-r--r--debian/patches/series2
-rw-r--r--debian/postinst23
-rw-r--r--debian/prerm25
-rwxr-xr-xdebian/rules14
-rw-r--r--debian/source/format1
-rw-r--r--waagent2335
20 files changed, 3204 insertions, 0 deletions
diff --git a/LICENSE-2.0.txt b/LICENSE-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c66328c
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,5 @@
+Windows Azure Linux Agent
+Copyright 2012 Microsoft Corporation
+
+This product includes software developed at
+Microsoft Corporation (http://www.microsoft.com/).
diff --git a/README b/README
new file mode 100644
index 0000000..054f797
--- /dev/null
+++ b/README
@@ -0,0 +1,352 @@
+Windows Azure Linux Agent User Guide
+
+Introduction
+The Windows Azure Linux Agent (waagent) manages VM interaction with the Windows
+Azure Fabric Controller. It provides the following functionality for Linux IaaS
+deployments:
+
+* Image Provisioning
+ - Creation of a user account.
+ - Configuring SSH authentication types.
+ - Deployment of SSH public keys and key pairs
+ - Setting the host name
+ - Publishing the host name to the platform DNS
+ - Reporting SSH host key fingerprint to the platform
+ - Resource Disk Management
+ - Formatting and mounting the resource disk
+ - Configuring swap space
+* Networking
+ - Manages routes to improve compatibility with platform DHCP servers
+ - Ensures the stability of the network interface name
+* Kernel
+ - Configuring virtual NUMA
+ - Consume Hyper-V entropy for /dev/random
+ - Configuring SCSI timeouts for the root device (which could be remote)
+* Diagnostics
+ - Console redirection to the serial port
+
+The information flow from the platform to the agent occurs via two channels:
+A TCP endpoint exposing a REST API and a boot-time attached DVD for IaaS
+deployments. The DVD includes an OVF-compliant configuration file that includes
+all provisioning information other than the actual SSH keypairs. The deployment
+configuration and topology are obtained over the REST API.
+
+Supported Linux Distributions
+1.CentOS 6.0+
+2.Ubuntu 12.04+
+3.Suse (SLES) 11SP2+
+4.Open Suse 12.1+
+
+Requirements
+Waagent depends on some system packages in order to function properly:
+1.Python 2.4+
+2.Openssl 1.0+
+3.Openssh 5.3+
+4.Filesystem utilities: sfdisk, fdisk, mkfs
+5.Password tools: chpasswd
+6.Text processing tools: sed, grep
+
+Installation
+Installation using an RPM or a DEB package is preferred. If installing
+manually,waagent should be copied to /usr/sbin/waagent and installed by
+running: /usr/sbin/waagent -install
+
+Command Line Options
+Flags
+-verbose: Increase verbosity of specified command
+-force: Skip interactive confirmation for some commands
+Commands
+-help: Lists the supported commands and flags.
+-install: Checks the system for required dependencies.
+ Creates the SysV init script (/etc/init.d/waagent),
+ the logrotate configuration file (/etc/logrotate.d/waagent),
+ and configures the image to run the init script on boot.
+ Writes sample configuration file to /etc/waagent.conf.
+ Any existing configuration file is moved to /etc/waagent.conf.old.
+ Detects kernel version and apply VNUMA workaround if necessary.
+ Moves udev rules that may interfere with networking
+ (/lib/udev/rules.d/75-persistent-net-generator.rules,
+ /etc/udev/rules.d/70-persistent-net.rules) to /var/lib/waagent/.
+-uninstall: Unregisters the init script from the system and deletes it.
+ Deletes the logrotate configuration and the waagent config file in
+ /etc/waagent.conf.
+ Automatic reverting of the VNUMA workaround is not supported – please edit
+ the GRUB configuration files by hand to re-enable NUMA if required.
+ Restores any moved udev rules.
+-deprovision: Attempt to clean the system and make it suitable for
+ re-provisioning. Deletes the following:
+ 1. All SSH host keys
+ (if Provisioning.RegenerateSshHostKeyPair is set in the configuration file)
+ 2. Nameserver configuration in /etc/resolv.conf
+ 3. Root password from /etc/shadow
+ (if Provisioning.DeleteRootPassword is set in the configuration file)
+ 4. Cached DHCP client leases.
+ 5. Resets host name to localhost.localdomain.
+ WARNING! This doesn’t guarantee that the image is cleared of all
+ sensitive information and suitable for redistribution.
+-deprovision+user: Everything under deprovision (above) and also deletes
+ the last provisioned user account and associated data.
+-version: Displays the version of waagent
+-serialconsole: Configures GRUB to mark ttyS0 (the first serial port) as
+ the boot console. This ensures that kernel bootup logs are sent to the
+ serial port and made available for debugging.
+-daemon: Run waagent as a daemon to manage interaction with the platform.
+ This argument is specified to waagent in the waagent init script.
+
+Configuration
+A configuration file (/etc/waagent.conf) controls the actions of waagent.
+A sample configuration file is shown below:
+
+#
+# Windows Azure Linux Agent Configuration
+#
+Role.StateConsumer=None
+Role.ConfigurationConsumer=None
+Role.TopologyConsumer=None
+Provisioning.Enabled=y
+Provisioning.DeleteRootPassword=n
+Provisioning.RegenerateSshHostKeyPair=y
+Provisioning.SshHostKeyPairType=rsa
+Provisioning.MonitorHostName=y
+ResourceDisk.Format=y
+ResourceDisk.Filesystem=ext4
+ResourceDisk.MountPoint=/mnt/resource
+ResourceDisk.EnableSwap=n
+ResourceDisk.SwapSizeMB=0
+LBProbeResponder=y
+Logs.Verbose=n
+OS.RootDeviceScsiTimeout=300
+OS.OpensslPath=None
+
+The various configuration options are described in detail below.
+Configuration options are of three types : Boolean, String or Integer.
+The Boolean configuration options can be specified as “y” or “n”.
+The special keyword “None” may be used for some string type configuration
+entries as detailed below.
+
+Role.StateConsumer:
+Type: String Default: None
+If a path to an executable program is specified, the program is invoked at
+two events.
+1.When the waagent has provisioned the image and the “Ready” state is about
+ to be reported to the Fabric. The argument specified to the program
+ will be “Ready”. waagent will not wait for the program to return before
+ continuing.
+2.When the waagent has received a shutdown request from the Fabric and is about
+ to shutdown the VM. The argument specified to the program will be “Shutdown”.
+ waagent will wait for the program to return before initiating the shutdown
+ process.
+
+Role.ConfigurationConsumer:
+Type: String Default: None
+If a path to an executable program is specified,
+the program is invoked when the Fabric indicates that a configuration file is
+available for the VM. The path to the XML configuration file is provided as an
+argument to the executable. This may be invoked multiple times whenever the
+configuration file changes. A sample file is provided in the Appendix. Please
+note that the XML schema used in this file may change in the future.
+The current path of this file is /var/lib/waagent/HostingEnvironmentConfig.xml.
+
+Role.TopologyConsumer:
+Type: String Default: None
+If a path to an executable program is specified,
+the program is invoked when the Fabric indicates that a new network topology
+layout is available for the VM.The path to the XML configuration file is
+provided as an argument to the executable. This may be invoked multiple times
+whenever the network topology changes (due to service healing for example).
+A sample file is provided in the Appendix. Please note that the XML schema
+used in this file may change in the future.
+The current location of this file is /var/lib/waagent/SharedConfig.xml.
+
+Provisioning.Enabled:
+Type: Boolean Default: y
+This allows the user to enable or disable the provisioning functionality in the
+agent. Valid values are “y” or “n”. If provisioning is disabled, SSH host and
+user keys in the image are preserved and any configuration specified in the
+Windows Azure provisioning API is ignored.
+
+Provisioning.DeleteRootPassword:
+Type: Boolean Default: n
+If set, the root password in the /etc/shadow file is erased during the
+provisioning process.
+
+Provisioning.RegenerateSshHostKeyPair:
+Type: Boolean Default: y
+If set, all SSH host key pairs (ecdsa, dsa and rsa) are deleted during the
+provisioning process from /etc/ssh/. And a single fresh key pair is generated.
+The encryption type for the fresh key pair is configurable by the
+Provisioning.SshHostKeyPairType entry. Please note that some distributions will
+re-create SSH key pairs for any missing encryption types when the SSH daemon is
+restarted (for example, upon a reboot).
+
+Provisioning.SshHostKeyPairType:
+Type: String Default: rsa
+This can be set to an encryption algorithm type that is supported by the SSH
+daemon on the VM. The typically supported values are “rsa”, “dsa” and “ecdsa”.
+Note that “putty.exe” on Windows does not support “ecdsa”. So, if you intend
+to use putty.exe on Windows to connect to a Linux deployment, please use
+“rsa” or “dsa”.
+
+Provisioning.MonitorHostName:
+Type: Boolean Default: y
+If set, waagent will monitor the Linux VM for hostname changes (as returned
+by the “hostname” command) and automatically update the networking
+configuration in the image to reflect the change. In order to push the name
+change to the DNS servers, networking will be restarted in the VM. This will
+result in brief loss of Internet connectivity.
+
+ResourceDisk.Format:
+Type: Boolean Default: y
+If set, the resource disk provided by the platform will be formatted and
+mounted by waagent if the filesystem type requested by the user in
+"ResourceDisk.Filesystem" is anything other than "ntfs". A single partition of
+type Linux (83) will be made available on the disk. Note that this partition
+will not be formatted if it can be successfully mounted.
+
+ResourceDisk.Filesystem:
+Type: String Default: ext4
+This specifies the filesystem type for the resource disk. Supported values vary
+by Linux distribution. If the string is X, then mkfs.X should be present on
+the Linux image.
+
+ResourceDisk.MountPoint:
+Type: String Default: /mnt/resource
+This specifies the path at which the resource disk is mounted.
+
+ResourceDisk.EnableSwap:
+Type: Boolean Default: n
+If set, a swap file (/swapfile) is created on the resource disk and added to
+the system swap space.
+
+ResourceDisk.SwapSizeMB:
+Type: Integer Default: 0
+The size of the swap file in megabytes.
+
+LBProbeResponder: Type: Boolean Default: y
+If set, waagent will respond to load balancer probes from the platform
+(if present).
+
+Logs.Verbose: Type: Boolean Default: n
+If set, log verbosity is boosted. Waagent logs to /var/log/waagent.log and
+leverages the system logrotate functionality to rotate logs.
+
+
+OS.RootDeviceScsiTimeout:
+Type: Integer Default: 300
+This configures the SCSI timeout in seconds on the root device. If not set,
+the system defaults are used.
+
+OS.OpensslPath:
+Type: String Default: None
+This can be used to specify an alternate path for the openssl binary to use
+for cryptographic operations.
+
+Appendix
+Sample Role Configuration File
+
+<?xml version="1.0" encoding="utf-8"?>
+<HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
+ <StoredCertificates>
+ <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
+ </StoredCertificates>
+ <Deployment name="a99549a92e38498f98cf2989330cd2f1" guid="{374ef9a2-de81-4412-ac87-e586fc869923}" incarnation="14">
+ <Service name="LinuxDemo1" guid="{00000000-0000-0000-0000-000000000000}" />
+ <ServiceInstance name="a99549a92e38498f98cf2989330cd2f1.4" guid="{250ac9df-e14c-4c5b-9cbc-f8a826ced0e7}" />
+ </Deployment>
+ <Incarnation number="1" instance="LinuxVM_IN_2" guid="{5c87ab8b-2f6a-4758-9f74-37e68c3e957b}" />
+ <Role guid="{47a04da2-d0b7-26e2-f039-b1f1ab11337a}" name="LinuxVM" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
+ <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.111026-1712.RuntimePackage_1.0.0.9.zip">
+ <CAS mode="full" />
+ <PrivilegeLevel mode="max" />
+ <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties></HostingEnvironmentSettings>
+ <ApplicationSettings>
+ <Setting name="__ModelData" value="&lt;m role=&quot;LinuxVM&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;LinuxVM&quot;>&lt;e name=&quot;HTTP&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;e name=&quot;SSH&quot; />&lt;/r>&lt;/m>" />
+ <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="…" />
+ <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2015-11-06T23:59:59.0000000-08:00" />
+ <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="rdos" />
+ <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" />
+ <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
+ <Setting name="startpage" value="Hello World!" />
+ <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" />
+ </ApplicationSettings>
+ <ResourceReferences>
+ <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="a99549a92e38498f98cf2989330cd2f1.LinuxVM.DiagnosticStore\" disableQuota="false" />
+ </ResourceReferences>
+ </HostingEnvironmentConfig>
+
+
+Sample Role Topology File
+<?xml version="1.0" encoding="utf-8"?>
+<SharedConfig version="1.0.0.0" goalStateIncarnation="2">
+ <Deployment name="a99549a92e38498f98cf2989330cd2f1" guid="{374ef9a2-de81-4412-ac87-e586fc869923}" incarnation="14">
+ <Service name="LinuxDemo1" guid="{00000000-0000-0000-0000-000000000000}" />
+ <ServiceInstance name="a99549a92e38498f98cf2989330cd2f1.4" guid="{250ac9df-e14c-4c5b-9cbc-f8a826ced0e7}" />
+ </Deployment>
+ <Incarnation number="1" instance="LinuxVM_IN_1" guid="{a7b94774-db5c-4007-8707-0b9e91fd808d}" />
+ <Role guid="{47a04da2-d0b7-26e2-f039-b1f1ab11337a}" name="LinuxVM" settleTimeSeconds="10" />
+ <LoadBalancerSettings timeoutSeconds="32" waitLoadBalancerProbeCount="8">
+ <Probes>
+ <Probe name="LinuxVM" />
+ <Probe name="03F7F19398C4358108B7ED059966EEBD" />
+ <Probe name="47194D0E3AB3FCAD621CAAF698EC82D8" />
+ </Probes>
+ </LoadBalancerSettings>
+ <OutputEndpoints>
+ <Endpoint name="LinuxVM:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
+ <Target instance="LinuxVM_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
+ <Target instance="LinuxVM_IN_1" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
+ <Target instance="LinuxVM_IN_2" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
+ </Endpoint>
+ </OutputEndpoints>
+ <Instances>
+ <Instance id="LinuxVM_IN_1" address="10.115.38.202">
+ <FaultDomains randomId="1" updateId="1" updateCount="2" />
+ <InputEndpoints>
+ <Endpoint name="HTTP" address="10.115.38.202:80" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.56.176:80" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="80" to="80" />
+ </LocalPorts>
+ </Endpoint>
+ <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.38.202:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="3389" to="3389" />
+ </LocalPorts>
+ <RemoteInstances>
+ <RemoteInstance instance="LinuxVM_IN_0" />
+ <RemoteInstance instance="LinuxVM_IN_2" />
+ </RemoteInstances>
+ </Endpoint>
+ <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" address="10.115.38.202:20000" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.56.176:3389" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="20000" to="20000" />
+ </LocalPorts>
+ </Endpoint>
+ <Endpoint name="SSH" address="10.115.38.202:22" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.56.176:22" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="22" to="22" />
+ </LocalPorts>
+ </Endpoint>
+ </InputEndpoints>
+ </Instance>
+ <Instance id="LinuxVM_IN_0" address="10.115.58.82">
+ <FaultDomains randomId="0" updateId="0" updateCount="2" />
+ <InputEndpoints>
+ <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.58.82:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="3389" to="3389" />
+ </LocalPorts>
+ </Endpoint>
+ </InputEndpoints>
+ </Instance>
+ <Instance id="LinuxVM_IN_2" address="10.115.58.148">
+ <FaultDomains randomId="0" updateId="2" updateCount="2" />
+ <InputEndpoints>
+ <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.58.148:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="3389" to="3389" />
+ </LocalPorts>
+ </Endpoint>
+ </InputEndpoints>
+ </Instance>
+ </Instances>
+</SharedConfig>
diff --git a/debian/README.source b/debian/README.source
new file mode 100644
index 0000000..7f12ed5
--- /dev/null
+++ b/debian/README.source
@@ -0,0 +1,7 @@
+version=3
+# Upstream have not yet released so its based on a git snapshot from:
+#
+# https://github.com/Windows-Azure/WALinuxAgent
+#
+# See the get-packaged-orig-source target in debian/rules for more
+# information.
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..2e0e6b4
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,9 @@
+walinuxagent (1.0~git20120606.c16f5e9-0ubuntu1) quantal; urgency=low
+
+ * Initial package import (LP: #1014864).
+ * Ubuntu specific modifications:
+ - Made resolvconf aware during deprovisioning
+ - Added Ubuntu upstart job
+ - Added ability to prevent agent startup.
+
+ -- Ben Howard <ben.howard@ubuntu.com> Fri, 22 Jun 2012 09:10:22 -0600
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..45a4fb7
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+8
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..7063adf
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,25 @@
+Source: walinuxagent
+Section: python
+Priority: extra
+Maintainer: Ben Howard <ben.howard@ubuntu.com>
+XSBC-Original-Maintainer: Microsoft Corporation <walinuxagent@microsoft.com>
+Build-Depends: debhelper (>= 8), python-all
+Standards-Version: 3.9.3
+XS-Python-Version: all
+Homepage: http://go.microsoft.com/fwlink/?LinkId=250998
+
+Package: walinuxagent
+Architecture: all
+Depends: python (>= 2.4),
+ openssl (>=1.0),
+ openssh-server (>=1:5.9p1),
+ passwd (>=4.1.4.2),
+ util-linux (>=2.0),
+ linux-image-extra-virtual,
+ ${misc:Depends},
+ ${python:Depends}
+Conflicts: network-manager
+Description: Windows Azure Linux Agent
+ The Windows Azure Linux Agent supports the provisioning and running of Linux
+ VMs in the Windows Azure cloud. This package should be installed on Linux
+ disk images that are built to run in the Windows Azure environment.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..fba1f44
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,21 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
+Upstream-Name: walinuxagent
+Upstream-Contact: Microsoft Corporation <walinuxagent@microsoft.com>
+Source: https://github.com/Windows-Azure/WALinuxAgent/
+
+Files: *
+Copyright: 2012, Microsoft Corporation <walinuxagent@microsoft.com>
+License: Apache-2.0
+
+Files: waaagent
+Copyright: 2012, Microsoft Corporation <walinuxagent@microsoft.com>
+ 2012, Ben Howard <ben.howard@canonical.com>
+License: Apache-2.0
+
+Files: debian/*
+Copyright: 2012, Ben Howard <ben.howard@canonical.com>
+License: Apache-2.0
+
+License: Apache-2.0
+ On Debian systems, the complete text of the Apache version 2.0 license
+ can be found in "/usr/share/common-licenses/Apache-2.0".
diff --git a/debian/default b/debian/default
new file mode 100644
index 0000000..a17174c
--- /dev/null
+++ b/debian/default
@@ -0,0 +1,2 @@
+# To disable the Windows Azure Agent, set WALINUXAGENT_ENABLED=0
+WALINUXAGENT_ENABLED=1
diff --git a/debian/dirs b/debian/dirs
new file mode 100644
index 0000000..500a2c6
--- /dev/null
+++ b/debian/dirs
@@ -0,0 +1 @@
+/usr/share/doc/walinuxagent
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..b924fd1
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,3 @@
+NOTICE
+LICENSE-2.0.txt
+README
diff --git a/debian/install b/debian/install
new file mode 100644
index 0000000..0c4c492
--- /dev/null
+++ b/debian/install
@@ -0,0 +1 @@
+waagent usr/sbin
diff --git a/debian/patches/000_ubuntu_init_resolvconf.patch b/debian/patches/000_ubuntu_init_resolvconf.patch
new file mode 100644
index 0000000..70c0097
--- /dev/null
+++ b/debian/patches/000_ubuntu_init_resolvconf.patch
@@ -0,0 +1,153 @@
+Description: Microsoft provided walinuxagent does not include the
+ required UFS module, is not resolvconf aware, and does not provide
+ an upstart script; this patch fixes all of that.
+Author: Ben Howard <ben.howard@ubuntu.com>
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/1014864
+Forwarded: yes
+
+--- a/waagent
++++ b/waagent
+@@ -1970,6 +1970,26 @@ esac
+ exit $RETVAL
+ """
+
++Init_Ubuntu = """\
++#walinuxagent - start Windows Azure agent
++
++description "walinuxagent"
++author "Ben Howard <ben.howard@canonical.com>"
++
++start on (filesystem and started rsyslog)
++
++pre-start script
++ if [ ! -x /usr/sbin/waagent ]; then
++ exit 1
++ fi
++
++ #Load the udf module
++ modprobe -b udf
++end script
++
++exec /usr/sbin/waagent -daemon
++"""
++
+ Init_Debian = """\
+ #!/bin/sh
+ ### BEGIN INIT INFO
+@@ -2135,18 +2155,30 @@ def Install():
+ os.remove(GetLastPathElement(a))
+ shutil.move(a, ".")
+ Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
+- filename = "waagent"
+- filepath = "/etc/init.d/" + filename
+- distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
+- if distro == 0:
+- Error("Unable to detect Linux Distribution.")
+- return 1
+- init = [[Init_RedHat, "chkconfig --add " + filename],
+- [Init_Debian, "update-rc.d " + filename + " defaults"],
+- [Init_Suse, "insserv " + filename]][distro - 1]
+- SetFileContents(filepath, init[0])
+- os.chmod(filepath, 0755)
+- Run(init[1])
++
++ if IsUbuntu():
++ # Support for Ubuntu's upstart configuration
++ filename="waagent.conf"
++ filepath = "/etc/init/" + filename
++ SetFileContents(filepath, Init_Ubuntu)
++ os.chmod(filepath, 0644)
++
++ else:
++ # Regular init.d configurations
++ filename = "waagent"
++ filepath = "/etc/init.d/" + filename
++
++ distro = IsRedHat() + IsDebian() * 2 + IsSuse()
++ if distro == 0:
++ Error("Unable to detect Linux Distribution.")
++ return 1
++ init = [[Init_RedHat, "chkconfig --add " + filename],
++ [Init_Debian, "update-rc.d " + filename + " defaults"],
++ [Init_Suse, "insserv " + filename]][distro - 1]
++ SetFileContents(filepath, init[0])
++ os.chmod(filepath, 0755)
++ Run(init[1])
++
+ if os.path.isfile("/etc/waagent.conf"):
+ try:
+ os.remove("/etc/waagent.conf.old")
+@@ -2179,17 +2211,26 @@ def Uninstall():
+ Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
+ except:
+ pass
++
++ filepath = "/etc/init.d/"
+ filename = "waagent"
+- a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
+- if a == 0:
+- Error("Unable to detect Linux Distribution.")
+- return 1
+- Run("service " + filename + " stop")
+- cmd = ["chkconfig --del " + filename,
+- "update-rc.d -f " + filename + " remove",
+- "insserv -r " + filename][a - 1]
+- Run(cmd)
+- for f in os.listdir(LibDir) + ["/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
++
++ if IsUbuntu():
++ Run("stop " + filename)
++ filepath = "/etc/init/"
++ filename = "waagent.conf"
++ else:
++ a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
++ if a == 0:
++ Error("Unable to detect Linux Distribution.")
++ return 1
++ Run("service " + filename + " stop")
++ cmd = ["chkconfig --del " + filename,
++ "update-rc.d -f " + filename + " remove",
++ "insserv -r " + filename][a - 1]
++ Run(cmd)
++
++ for f in os.listdir(LibDir) + [filepath + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
+ try:
+ os.remove(f)
+ except:
+@@ -2217,7 +2258,12 @@ def Deprovision(force, deluser):
+
+ print("WARNING! The waagent service will be stopped.")
+ print("WARNING! All SSH host key pairs will be deleted.")
+- print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
++
++ if IsUbuntu():
++ print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.")
++ else:
++ print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
++
+ print("WARNING! Cached DHCP leases will be deleted.")
+
+ delRootPass = Config.get("Provisioning.DeleteRootPassword")
+@@ -2253,7 +2299,21 @@ def Deprovision(force, deluser):
+ Run("rm -f " + a + "/*")
+
+ # Clear LibDir, remove nameserver and root bash history
+- for f in os.listdir(LibDir) + ["/etc/resolv.conf", "/root/.bash_history", "/var/log/waagent.log"]:
++ fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log" ]
++
++ # Ubuntu uses resolvconf, so we want to preserve the ability to use resolvconf
++ if IsUbuntu():
++ if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
++ Log("resolvconf is not configured. Removing /etc/resolv.conf")
++ fileBlackList.append('/etc/resolv.conf')
++ else:
++ Log("resolvconf is enabled; leaving /etc/resolv.conf intact")
++ resolvConfD = '/etc/resolvconf/resolv.conf.d/'
++ fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'originial' ])
++ else:
++ fileBlackList.append(os.listdir(LibDir) + '/etc/resolv.conf')
++
++ for f in os.listdir(LibDir) + fileBlackList:
+ try:
+ os.remove(f)
+ except:
diff --git a/debian/patches/001_ubuntu_agent_startup.patch b/debian/patches/001_ubuntu_agent_startup.patch
new file mode 100644
index 0000000..118bdb0
--- /dev/null
+++ b/debian/patches/001_ubuntu_agent_startup.patch
@@ -0,0 +1,22 @@
+Description: Enablement/disablement via /etc/default/waagent
+ Allow users to control the startup of waagent via /etc/default/waagent.
+ Setting "WAAGENT_ENABLED=0" will turn off the agent at boot time.
+Author: Ben Howard <ben.howard@ubuntu.com>
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/1014864
+
+--- a/waagent
++++ b/waagent
+@@ -1979,6 +1979,13 @@ author "Ben Howard <ben.howard@canonical
+ start on (filesystem and started rsyslog)
+
+ pre-start script
++
++ [ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent
++
++ if [ "$WALINUXAGENT_ENABLED" != "1" ]; then
++ exit 1
++ fi
++
+ if [ ! -x /usr/sbin/waagent ]; then
+ exit 1
+ fi
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..233ee46
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,2 @@
+000_ubuntu_init_resolvconf.patch
+001_ubuntu_agent_startup.patch
diff --git a/debian/postinst b/debian/postinst
new file mode 100644
index 0000000..b18aa8e
--- /dev/null
+++ b/debian/postinst
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -e
+case "$1" in
+ configure)
+ waagent --setup --force
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ if [ -f /etc/init/waagent ]; then
+ rm /etc/init/waagent
+ fi
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
+
diff --git a/debian/prerm b/debian/prerm
new file mode 100644
index 0000000..22c87b2
--- /dev/null
+++ b/debian/prerm
@@ -0,0 +1,25 @@
+#!/bin/sh
+set -e
+case "$1" in
+ purge)
+ rm /etc/waagent.conf > /dev/null || true
+ ;;
+
+ remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ if [ -x /usr/sbin/waagent ]; then
+ waagent --uninstall
+ fi
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..7923b65
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,14 @@
+#!/usr/bin/make -f
+
+DEB_UPSTREAM_VERSION=$(shell dpkg-parsechangelog | sed -rne 's,^Version: ([^-]+).*,\1,p')
+ORIG_COMMIT=$(shell echo $(DEB_UPSTREAM_VERSION) | cut -f3 -d\. )
+ORIG_SRC=https://github.com/Windows-Azure/WALinuxAgent
+
+get-packaged-orig-source:
+ git clone --separate-git-dir=.git \
+ $(ORIG_SRC) orig_source
+ git archive --format=tar.gz $(ORIG_COMMIT) \
+ -o walinuxagent_$(DEB_UPSTREAM_VERSION).orig.tar.gz
+
+%:
+ dh $@ --with python2
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/waagent b/waagent
new file mode 100644
index 0000000..c6c8440
--- /dev/null
+++ b/waagent
@@ -0,0 +1,2335 @@
+#!/usr/bin/python
+#
+# Windows Azure Linux Agent
+#
+# Copyright 2012 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+#
+
+import array
+import base64
+import httplib
+import os
+import os.path
+import platform
+import pwd
+import re
+import shutil
+import socket
+import SocketServer
+import struct
+import sys
+import tempfile
+import textwrap
+import threading
+import time
+import traceback
+import xml.dom.minidom
+
+GuestAgentName = "WALinuxAgent"
+GuestAgentLongName = "Windows Azure Linux Agent"
+GuestAgentVersion = "rd_wala.120504-1323"
+ProtocolVersion = "2011-12-31"
+
+Config = None
+LinuxDistro = "UNKNOWN"
+Verbose = False
+WaAgent = None
+DiskActivated = False
+Openssl = "openssl"
+
+PossibleEthernetInterfaces = ["seth0", "seth1", "eth0", "eth1"]
+RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
+ "/etc/udev/rules.d/70-persistent-net.rules" ]
+VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
+EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
+LibDir = "/var/lib/waagent"
+
+# This lets us index into a string or an array of integers transparently.
+def Ord(a):
+ if type(a) == type("a"):
+ a = ord(a)
+ return a
+
+def IsWindows():
+ return (platform.uname()[0] == "Windows")
+
+def IsLinux():
+ return (platform.uname()[0] == "Linux")
+
+def DetectLinuxDistro():
+ global LinuxDistro
+ if os.path.isfile("/etc/redhat-release"):
+ LinuxDistro = "RedHat"
+ return True
+ if os.path.isfile("/etc/lsb-release") and "Ubuntu" in GetFileContents("/etc/lsb-release"):
+ LinuxDistro = "Ubuntu"
+ return True
+ if os.path.isfile("/etc/debian_version"):
+ LinuxDistro = "Debian"
+ return True
+ if os.path.isfile("/etc/SuSE-release"):
+ LinuxDistro = "Suse"
+ return True
+ return False
+
+def IsRedHat():
+ return "RedHat" in LinuxDistro
+
+def IsUbuntu():
+ return "Ubuntu" in LinuxDistro
+
+def IsDebian():
+ return IsUbuntu() or "Debian" in LinuxDistro
+
+def IsSuse():
+ return "Suse" in LinuxDistro
+
+def UsesRpm():
+ return IsRedHat() or IsSuse()
+
+def UsesDpkg():
+ return IsDebian()
+
+def GetLastPathElement(path):
+ return path.rsplit('/', 1)[1]
+
+def GetFileContents(filepath):
+ file = None
+ try:
+ file = open(filepath)
+ except:
+ return None
+ if file == None:
+ return None
+ try:
+ return file.read()
+ finally:
+ file.close()
+
+def SetFileContents(filepath, contents):
+ file = open(filepath, "w")
+ try:
+ file.write(contents)
+ finally:
+ file.close()
+
+def AppendFileContents(filepath, contents):
+ file = open(filepath, "a")
+ try:
+ file.write(contents)
+ finally:
+ file.close()
+
+def ReplaceFileContentsAtomic(filepath, contents):
+ handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
+ try:
+ os.write(handle, contents)
+ finally:
+ os.close(handle)
+ try:
+ os.rename(temp, filepath)
+ return
+ except:
+ pass
+ os.remove(filepath)
+ os.rename(temp, filepath)
+
+def GetLineStartingWith(prefix, filepath):
+ for line in GetFileContents(filepath).split('\n'):
+ if line.startswith(prefix):
+ return line
+ return None
+
+def Run(a):
+ LogIfVerbose(a)
+ return os.system(a)
+
+def GetNodeTextData(a):
+ for b in a.childNodes:
+ if b.nodeType == b.TEXT_NODE:
+ return b.data
+
+def GetHome():
+ home = None
+ try:
+ home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
+ except:
+ pass
+ if (home == None) or (home.startswith("/") == False):
+ home = "/home"
+ return home
+
+def ChangeOwner(filepath, user):
+ p = None
+ try:
+ p = pwd.getpwnam(user)
+ except:
+ pass
+ if p != None:
+ os.chown(filepath, p[2], p[3])
+
+def CreateDir(dirpath, user, mode):
+ try:
+ os.makedirs(dirpath, mode)
+ except:
+ pass
+ ChangeOwner(dirpath, user)
+
+def CreateAccount(user, password, expiration, thumbprint):
+ if IsWindows():
+ Log("Skipping CreateAccount on Windows")
+ return None
+ userentry = None
+ try:
+ userentry = pwd.getpwnam(user)
+ except:
+ pass
+ uidmin = None
+ try:
+ uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
+ except:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry != None and userentry[2] < uidmin:
+ Error("CreateAccount: " + user + " is a system user. Will not set password.")
+ return "Failed to set password for system user: " + user + " (0x06)."
+ if userentry == None:
+ command = "useradd -m " + user
+ if expiration != None:
+ command += " -e " + expiration.split('.')[0]
+ if Run(command):
+ Error("Failed to create user account: " + user)
+ return "Failed to create user account: " + user + " (0x07)."
+ else:
+ Log("CreateAccount: " + user + " already exists. Will update password.")
+ if password != None:
+ os.popen("chpasswd", "w").write(user + ":" + password + "\n")
+ try:
+ if password == None:
+ SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
+ else:
+ SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
+ os.chmod("/etc/sudoers.d/waagent", 0440)
+ except:
+ Error("CreateAccount: Failed to configure sudo access for user.")
+ return "Failed to configure sudo privileges (0x08)."
+ home = GetHome()
+ if thumbprint != None:
+ dir = home + "/" + user + "/.ssh"
+ CreateDir(dir, user, 0700)
+ pub = dir + "/id_rsa.pub"
+ prv = dir + "/id_rsa"
+ Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
+ SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
+ for f in [pub, prv]:
+ os.chmod(f, 0600)
+ ChangeOwner(f, user)
+ SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
+ ChangeOwner(dir + "/authorized_keys", user)
+ Log("Created user account: " + user)
+ return None
+
+def DeleteAccount(user):
+ if IsWindows():
+ Log("Skipping DeleteAccount on Windows")
+ return
+ userentry = None
+ try:
+ userentry = pwd.getpwnam(user)
+ except:
+ pass
+ if userentry == None:
+ Error("DeleteAccount: " + user + " not found.")
+ return
+ uidmin = None
+ try:
+ uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
+ except:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry[2] < uidmin:
+ Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
+ return
+ Run("userdel -f -r " + user)
+ try:
+ os.remove("/etc/sudoers.d/waagent")
+ except:
+ pass
+ return
+
+def ReloadSshd():
+ name = None
+ if IsRedHat() or IsSuse():
+ name = "sshd"
+ if IsDebian():
+ name = "ssh"
+ if name == None:
+ return
+ if not Run("service " + name + " status | grep running"):
+ Run("service " + name + " reload")
+
+def IsInRangeInclusive(a, low, high):
+ return (a >= low and a <= high)
+
+def IsPrintable(ch):
+ return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
+
+def HexDump(buffer, size):
+ if size < 0:
+ size = len(buffer)
+ result = ""
+ for i in range(0, size):
+ if (i % 16) == 0:
+ result += "%06X: " % i
+ byte = struct.unpack("B", buffer[i])[0]
+ result += "%02X " % byte
+ if (i & 15) == 7:
+ result += " "
+ if ((i + 1) % 16) == 0 or (i + 1) == size:
+ j = i
+ while ((j + 1) % 16) != 0:
+ result += " "
+ if (j & 7) == 7:
+ result += " "
+ j += 1
+ result += " "
+ for j in range(i - (i % 16), i + 1):
+ byte = struct.unpack("B", buffer[j])[0]
+ k = '.'
+ if IsPrintable(byte):
+ k = chr(byte)
+ result += k
+ if (i + 1) != size:
+ result += "\n"
+ return result
+
+def ThrottleLog(counter):
+ # Log everything up to 10, every 10 up to 100, then every 100.
+ return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
+
+def Logger():
+ class T(object):
+ def __init__(self):
+ self.File = None
+
+ self = T()
+
+ def LogToFile(message):
+ FilePath = ["/var/log/waagent.log", "waagent.log"][IsWindows()]
+ if not os.path.isfile(FilePath) and self.File != None:
+ self.File.close()
+ self.File = None
+ if self.File == None:
+ self.File = open(FilePath, "a")
+ self.File.write(message + "\n")
+ self.File.flush()
+
+ def Log(message):
+ LogWithPrefix("", message)
+
+ def LogWithPrefix(prefix, message):
+ t = time.localtime()
+ t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
+ t += prefix
+ for line in message.split('\n'):
+ line = t + line
+ print(line)
+ LogToFile(line)
+
+ return Log, LogWithPrefix
+
+Log, LogWithPrefix = Logger()
+
+def NoLog(message):
+ pass
+
+def LogIfVerbose(message):
+ if Verbose == True:
+ Log(message)
+
+def LogWithPrefixIfVerbose(prefix, message):
+ if Verbose == True:
+ LogWithPrefix(prefix, message)
+
+def Warn(message):
+ LogWithPrefix("WARNING:", message)
+
+def Error(message):
+ LogWithPrefix("ERROR:", message)
+
+def ErrorWithPrefix(prefix, message):
+ LogWithPrefix("ERROR:" + prefix, message)
+
+def Linux_ioctl_GetIpv4Address(ifname):
+ import fcntl
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
+
+def Linux_ioctl_GetInterfaceMac(ifname):
+ import fcntl
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', ifname[:15]))
+ return ''.join(['%02X' % Ord(char) for char in info[18:24]])
+
+def GetIpv4Address():
+ if IsLinux():
+ for ifname in PossibleEthernetInterfaces:
+ try:
+ return Linux_ioctl_GetIpv4Address(ifname)
+ except IOError, e:
+ pass
+ else:
+ try:
+ return socket.gethostbyname(socket.gethostname())
+ except Exception, e:
+ ErrorWithPrefix("GetIpv4Address:", str(e))
+ ErrorWithPrefix("GetIpv4Address:", traceback.format_exc())
+
+def HexStringToByteArray(a):
+ b = ""
+ for c in range(0, len(a) / 2):
+ b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
+ return b
+
+def GetMacAddress():
+ if IsWindows():
+ # Windows: Physical Address. . . . . . . . . : 00-15-17-79-00-7F\n
+ a = "ipconfig /all | findstr /c:\"Physical Address\" | findstr /v \"00-00-00-00-00-00-00\""
+ a = os.popen(a).read()
+ a = re.sub("\s+$", "", a)
+ a = re.sub(".+ ", "", a)
+ a = re.sub(":", "", a)
+ a = re.sub("-", "", a)
+ else:
+ for ifname in PossibleEthernetInterfaces:
+ try:
+ a = Linux_ioctl_GetInterfaceMac(ifname)
+ break
+ except IOError, e:
+ pass
+ return HexStringToByteArray(a)
+
+def DeviceForIdePort(n):
+ if n > 3:
+ return None
+ g0 = "00000000"
+ if n > 1:
+ g0 = "00000001"
+ n = n - 2
+ device = None
+ path="/sys/bus/vmbus/devices/"
+ for vmbus in os.listdir(path):
+ guid=GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
+ if guid[0] == g0 and guid[1] == "000" + str(n):
+ for root, dirs, files in os.walk(path + vmbus):
+ if root.endswith("/block"):
+ device = dirs[0]
+ break
+ break
+ return device
+
+class Util(object):
+ def _HttpGet(self, url, headers):
+ LogIfVerbose("HttpGet(" + url + ")")
+ maxRetry = 2
+ if url.startswith("http://"):
+ url = url[7:]
+ url = url[url.index("/"):]
+ for retry in range(0, maxRetry + 1):
+ strRetry = str(retry)
+ log = [NoLog, Log][retry > 0]
+ log("retry HttpGet(" + url + "),retry=" + strRetry)
+ response = None
+ strStatus = "None"
+ try:
+ httpConnection = httplib.HTTPConnection(self.Endpoint)
+ if headers == None:
+ request = httpConnection.request("GET", url)
+ else:
+ request = httpConnection.request("GET", url, None, headers)
+ response = httpConnection.getresponse()
+ strStatus = str(response.status)
+ except:
+ pass
+ log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ if response == None or response.status != httplib.OK:
+ Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ if retry == maxRetry:
+ Log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ return None
+ else:
+ Log("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ time.sleep(10)
+ else:
+ log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ return response.read()
+
+ def HttpGetWithoutHeaders(self, url):
+ return self._HttpGet(url, None)
+
+ def HttpGetWithHeaders(self, url):
+ return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion})
+
+ def HttpSecureGetWithHeaders(self, url, transportCert):
+ return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName,
+ "x-ms-version": ProtocolVersion,
+ "x-ms-cipher-name": "DES_EDE3_CBC",
+ "x-ms-guest-agent-public-x509-cert": transportCert})
+
+ def HttpPost(self, url, data):
+ LogIfVerbose("HttpPost(" + url + ")")
+ maxRetry = 2
+ for retry in range(0, maxRetry + 1):
+ strRetry = str(retry)
+ log = [NoLog, Log][retry > 0]
+ log("retry HttpPost(" + url + "),retry=" + strRetry)
+ response = None
+ strStatus = "None"
+ try:
+ httpConnection = httplib.HTTPConnection(self.Endpoint)
+ request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName,
+ "Content-Type": "text/xml; charset=utf-8",
+ "x-ms-version": ProtocolVersion})
+ response = httpConnection.getresponse()
+ strStatus = str(response.status)
+ except:
+ pass
+ log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED):
+ Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ if retry == maxRetry:
+ Log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ return None
+ else:
+ Log("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ time.sleep(10)
+ else:
+ log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
+ return response
+
+def LoadBalancerProbeServer(port):
+
+ class T(object):
+ def __init__(self, port):
+ enabled = Config.get("LBProbeResponder")
+ if enabled != None and enabled.lower().startswith("n"):
+ return
+ self.ProbeCounter = 0
+ self.server = SocketServer.TCPServer((GetIpv4Address(), port), TCPHandler)
+ self.server_thread = threading.Thread(target = self.server.serve_forever)
+ self.server_thread.setDaemon(True)
+ self.server_thread.start()
+
+ def shutdown(self):
+ global EnableLoadBalancerProbes
+ if not EnableLoadBalancerProbes:
+ return
+ self.server.shutdown()
+
+ class TCPHandler(SocketServer.BaseRequestHandler):
+ def GetHttpDateTimeNow(self):
+ # Date: Fri, 25 Mar 2011 04:53:10 GMT
+ return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
+
+ def handle(self):
+ context.ProbeCounter = (context.ProbeCounter + 1) % 1000000
+ log = [NoLog, LogIfVerbose][ThrottleLog(context.ProbeCounter)]
+ strCounter = str(context.ProbeCounter)
+ if context.ProbeCounter == 1:
+ Log("Receiving LB probes.")
+ log("Received LB probe # " + strCounter)
+ self.request.recv(1024)
+ self.request.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: text/html\r\nDate: " + self.GetHttpDateTimeNow() + "\r\n\r\nOK")
+
+ context = T(port)
+ return context
+
+class ConfigurationProvider(object):
+ def __init__(self):
+ self.values = dict()
+ if os.path.isfile("/etc/waagent.conf") == False:
+ raise Exception("Missing configuration in /etc/waagent.conf")
+ try:
+ for line in GetFileContents("/etc/waagent.conf").split('\n'):
+ if not line.startswith("#") and "=" in line:
+ parts = line.split()[0].split('=')
+ value = parts[1].strip("\" ")
+ if value != "None":
+ self.values[parts[0]] = value
+ else:
+ self.values[parts[0]] = None
+ except:
+ Error("Unable to parse /etc/waagent.conf")
+ raise
+ return
+
+ def get(self, key):
+ return self.values.get(key)
+
+class EnvMonitor(object):
+ def __init__(self):
+ self.shutdown = False
+ self.HostName = socket.gethostname()
+ self.server_thread = threading.Thread(target = self.monitor)
+ self.server_thread.setDaemon(True)
+ self.server_thread.start()
+ self.published = False
+
+ def monitor(self):
+ publish = Config.get("Provisioning.MonitorHostName")
+ dhcpcmd = "pidof dhclient"
+ if IsSuse():
+ dhcpcmd = "pidof dhcpcd"
+ if IsDebian():
+ dhcpcmd = "pidof dhclient3"
+ dhcppid = os.popen(dhcpcmd).read()
+ while not self.shutdown:
+ for a in RulesFiles:
+ if os.path.isfile(a):
+ if os.path.isfile(GetLastPathElement(a)):
+ os.remove(GetLastPathElement(a))
+ shutil.move(a, ".")
+ Log("EnvMonitor: Moved " + a + " -> " + LibDir)
+ if publish != None and publish.lower().startswith("y"):
+ try:
+ if socket.gethostname() != self.HostName:
+ Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
+ self.HostName = socket.gethostname()
+ WaAgent.UpdateAndPublishHostName(self.HostName)
+ dhcppid = os.popen(dhcpcmd).read()
+ self.published = True
+ except:
+ pass
+ else:
+ self.published = True
+ pid = ""
+ if not os.path.isdir("/proc/" + dhcppid.strip()):
+ pid = os.popen(dhcpcmd).read()
+ if pid != "" and pid != dhcppid:
+ Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
+ WaAgent.RestoreRoutes()
+ dhcppid = pid
+ time.sleep(5)
+
+ def SetHostName(self, name):
+ if socket.gethostname() == name:
+ self.published = True
+ else:
+ Run("hostname " + name)
+
+ def IsNamePublished(self):
+ return self.published
+
+ def shutdown(self):
+ self.shutdown = True
+ self.server_thread.join()
+
+class Certificates(object):
+#
+# <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
+# <Version>2010-12-15</Version>
+# <Incarnation>2</Incarnation>
+# <Format>Pkcs7BlobWithPfxContents</Format>
+# <Data>MIILTAY...
+# </Data>
+# </CertificateFile>
+#
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ self.Incarnation = None
+ self.Role = None
+
+ def Parse(self, xmlText):
+ self.reinitialize()
+ SetFileContents("Certificates.xml", xmlText)
+ dom = xml.dom.minidom.parseString(xmlText)
+ for a in [ "CertificateFile", "Version", "Incarnation",
+ "Format", "Data", ]:
+ if not dom.getElementsByTagName(a):
+ Error("Certificates.Parse: Missing " + a)
+ return None
+ node = dom.childNodes[0]
+ if node.localName != "CertificateFile":
+ Error("Certificates.Parse: root not CertificateFile")
+ return None
+ SetFileContents("Certificates.p7m",
+ "MIME-Version: 1.0\n"
+ + "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
+ + "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
+ + "Content-Transfer-Encoding: base64\n\n"
+ + GetNodeTextData(dom.getElementsByTagName("Data")[0]))
+ if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
+ Error("Certificates.Parse: Failed to extract certificates from CMS message.")
+ return self
+ # There may be multiple certificates in this package. Split them.
+ file = open("Certificates.pem")
+ pindex = 1
+ cindex = 1
+ output = open("temp.pem", "w")
+ for line in file.readlines():
+ output.write(line)
+ if line.startswith("-----END PRIVATE KEY-----") or line.startswith("-----END CERTIFICATE-----"):
+ output.close()
+ if line.startswith("-----END PRIVATE KEY-----"):
+ os.rename("temp.pem", str(pindex) + ".prv")
+ pindex += 1
+ else:
+ os.rename("temp.pem", str(cindex) + ".crt")
+ cindex += 1
+ output = open("temp.pem", "w")
+ output.close()
+ os.remove("temp.pem")
+ keys = dict()
+ index = 1
+ filename = str(index) + ".crt"
+ while os.path.isfile(filename):
+ thumbprint = os.popen(Openssl + " x509 -in " + filename + " -fingerprint -noout").read().rstrip().split('=')[1].replace(':', '').upper()
+ pubkey=os.popen(Openssl + " x509 -in " + filename + " -pubkey -noout").read()
+ keys[pubkey] = thumbprint
+ os.rename(filename, thumbprint + ".crt")
+ os.chmod(thumbprint + ".crt", 0600)
+ if IsRedHat():
+ Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + thumbprint + ".crt")
+ index += 1
+ filename = str(index) + ".crt"
+ index = 1
+ filename = str(index) + ".prv"
+ while os.path.isfile(filename):
+ pubkey = os.popen(Openssl + " rsa -in " + filename + " -pubout").read()
+ os.rename(filename, keys[pubkey] + ".prv")
+ os.chmod(keys[pubkey] + ".prv", 0600)
+ if IsRedHat():
+ Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + keys[pubkey] + ".prv")
+ index += 1
+ filename = str(index) + ".prv"
+ return self
+
+class SharedConfig(object):
+#
+# <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
+# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
+# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
+# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
+# </Deployment>
+# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
+# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
+# <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
+# <Probes>
+# <Probe name="MachineRole" />
+# <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
+# <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
+# </Probes>
+# </LoadBalancerSettings>
+# <OutputEndpoints>
+# <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
+# <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
+# </Endpoint>
+# </OutputEndpoints>
+# <Instances>
+# <Instance id="MachineRole_IN_0" address="10.115.153.75">
+# <FaultDomains randomId="0" updateId="0" updateCount="0" />
+# <InputEndpoints>
+# <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">
+# <LocalPorts>
+# <LocalPortRange from="80" to="80" />
+# </LocalPorts>
+# </Endpoint>
+# <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+# <LocalPorts>
+# <LocalPortRange from="3389" to="3389" />
+# </LocalPorts>
+# </Endpoint>
+# <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">
+# <LocalPorts>
+# <LocalPortRange from="20000" to="20000" />
+# </LocalPorts>
+# </Endpoint>
+# </InputEndpoints>
+# </Instance>
+# </Instances>
+# </SharedConfig>
+#
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ self.Deployment = None
+ self.Incarnation = None
+ self.Role = None
+ self.LoadBalancerSettings = None
+ self.OutputEndpoints = None
+ self.Instances = None
+
+ def Parse(self, xmlText):
+ self.reinitialize()
+ SetFileContents("SharedConfig.xml", xmlText)
+ dom = xml.dom.minidom.parseString(xmlText)
+ for a in [ "SharedConfig", "Deployment", "Service",
+ "ServiceInstance", "Incarnation", "Role", ]:
+ if not dom.getElementsByTagName(a):
+ Error("SharedConfig.Parse: Missing " + a)
+ return None
+ node = dom.childNodes[0]
+ if node.localName != "SharedConfig":
+ Error("SharedConfig.Parse: root not SharedConfig")
+ return None
+ program = Config.get("Role.TopologyConsumer")
+ if program != None:
+ os.spawnl(os.P_NOWAIT, program, program, LibDir + "/SharedConfig.xml")
+ return self
+
+class HostingEnvironmentConfig(object):
+#
+# <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
+# <StoredCertificates>
+# <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
+# </StoredCertificates>
+# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
+# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
+# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
+# </Deployment>
+# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
+# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
+# <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip">
+# <CAS mode="full" />
+# <PrivilegeLevel mode="max" />
+# <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties>
+# </HostingEnvironmentSettings>
+# <ApplicationSettings>
+# <Setting name="__ModelData" value="&lt;m role=&quot;MachineRole&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;MachineRole&quot;>&lt;e name=&quot;a&quot; />&lt;e name=&quot;b&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;/r>&lt;/m>" />
+# <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." />
+# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcN..." />
+# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2022-07-23T23:59:59.0000000-07:00" />
+# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test" />
+# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" />
+# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
+# <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" />
+# </ApplicationSettings>
+# <ResourceReferences>
+# <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" />
+# </ResourceReferences>
+# </HostingEnvironmentConfig>
+#
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ self.StoredCertificates = None
+ self.Deployment = None
+ self.Incarnation = None
+ self.Role = None
+ self.HostingEnvironmentSettings = None
+ self.ApplicationSettings = None
+ self.Certificates = None
+ self.ResourceReferences = None
+
+ def Parse(self, xmlText):
+ self.reinitialize()
+ SetFileContents("HostingEnvironmentConfig.xml", xmlText)
+ dom = xml.dom.minidom.parseString(xmlText)
+ for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
+ "ServiceInstance", "Incarnation", "Role", ]:
+ if not dom.getElementsByTagName(a):
+ Error("HostingEnvironmentConfig.Parse: Missing " + a)
+ return None
+ node = dom.childNodes[0]
+ if node.localName != "HostingEnvironmentConfig":
+ Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
+ return None
+ self.ApplicationSettings = dom.getElementsByTagName("Setting")
+ self.Certificates = dom.getElementsByTagName("StoredCertificate")
+ return self
+
+ def DecryptPassword(self, e):
+ SetFileContents("password.p7m",
+ "MIME-Version: 1.0\n"
+ + "Content-Disposition: attachment; filename=\"password.p7m\"\n"
+ + "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
+ + "Content-Transfer-Encoding: base64\n\n"
+ + textwrap.fill(e, 64))
+ return os.popen(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem").read()
+
+ def ActivateResourceDisk(self):
+ global DiskActivated
+ if IsWindows():
+ DiskActivated = True
+ Log("Skipping ActivateResourceDisk on Windows")
+ return
+ format = Config.get("ResourceDisk.Format")
+ if format == None or format.lower().startswith("n"):
+ DiskActivated = True
+ return
+ device = DeviceForIdePort(1)
+ if device == None:
+ Error("ActivateResourceDisk: Unable to detect disk topology.")
+ return
+ device = "/dev/" + device
+ for entry in os.popen("mount").read().split():
+ if entry.startswith(device + "1"):
+ Log("ActivateResourceDisk: " + device + "1 is already mounted.")
+ DiskActivated = True
+ return
+ mountpoint = Config.get("ResourceDisk.MountPoint")
+ if mountpoint == None:
+ mountpoint = "/mnt/resource"
+ CreateDir(mountpoint, "root", 0755)
+ fs = Config.get("ResourceDisk.Filesystem")
+ if fs == None:
+ fs = "ext3"
+ if os.popen("sfdisk -q -c " + device + " 1").read().rstrip() == "7" and fs != "ntfs":
+ Run("sfdisk -c " + device + " 1 83")
+ Run("mkfs." + fs + " " + device + "1")
+ if Run("mount " + device + "1 " + mountpoint):
+ Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).")
+ return
+ Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs)
+ DiskActivated = True
+ swap = Config.get("ResourceDisk.EnableSwap")
+ if swap == None or swap.lower().startswith("n"):
+ return
+ sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
+ if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
+ os.remove(mountpoint + "/swapfile")
+ if not os.path.isfile(mountpoint + "/swapfile"):
+ Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
+ Run("mkswap " + mountpoint + "/swapfile")
+ if not Run("swapon " + mountpoint + "/swapfile"):
+ Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
+ else:
+ Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
+
+ def Process(self):
+ if DiskActivated == False:
+ diskThread = threading.Thread(target = self.ActivateResourceDisk)
+ diskThread.start()
+ User = None
+ Pass = None
+ Expiration = None
+ Thumbprint = None
+ for b in self.ApplicationSettings:
+ sname = b.getAttribute("name")
+ svalue = b.getAttribute("value")
+ if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword":
+ Pass = self.DecryptPassword(svalue)
+ elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername":
+ User = svalue
+ elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration":
+ Expiration = svalue
+ elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption":
+ Thumbprint = svalue.split(':')[1].upper()
+ if User != None and Pass != None:
+ if User != "root" and User != "" and Pass != "":
+ CreateAccount(User, Pass, Expiration, Thumbprint)
+ else:
+ Error("Not creating user account: user=" + User + " pass=" + Pass)
+ for c in self.Certificates:
+ cname = c.getAttribute("name")
+ csha1 = c.getAttribute("certificateId").split(':')[1].upper()
+ cpath = c.getAttribute("storeName")
+ clevel = c.getAttribute("configurationLevel")
+ if os.path.isfile(csha1 + ".prv"):
+ Log("Private key with thumbprint: " + csha1 + " was retrieved.")
+ if os.path.isfile(csha1 + ".crt"):
+ Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
+ program = Config.get("Role.ConfigurationConsumer")
+ if program != None:
+ os.spawnl(os.P_NOWAIT, program, program, LibDir + "/HostingEnvironmentConfig.xml")
+
+class GoalState(Util):
+#
+# <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
+# <Version>2010-12-15</Version>
+# <Incarnation>1</Incarnation>
+# <Machine>
+# <ExpectedState>Started</ExpectedState>
+# <LBProbePorts>
+# <Port>16001</Port>
+# </LBProbePorts>
+# </Machine>
+# <Container>
+# <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
+# <RoleInstanceList>
+# <RoleInstance>
+# <InstanceId>MachineRole_IN_0</InstanceId>
+# <State>Started</State>
+# <Configuration>
+# <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=hostingEnvironmentConfig&amp;incarnation=1</HostingEnvironmentConfig>
+# <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=sharedConfig&amp;incarnation=1</SharedConfig>
+# <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&amp;incarnation=1</Certificates>
+# </Configuration>
+# </RoleInstance>
+# </RoleInstanceList>
+# </Container>
+# </GoalState>
+#
+# There is only one Role for VM images.
+#
+# Of primary interest is:
+# Machine/ExpectedState -- this is how shutdown is requested
+# LBProbePorts -- an http server needs to run here
+# We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
+# And of course, Incarnation
+#
+ def __init__(self, Agent):
+ self.Agent = Agent
+ self.Endpoint = Agent.Endpoint
+ self.TransportCert = Agent.TransportCert
+ self.reinitialize()
+
+ def reinitialize(self):
+ self.Incarnation = None # integer
+ self.ExpectedState = None # "Started" or "Stopped"
+ self.HostingEnvironmentConfigUrl = None
+ self.HostingEnvironmentConfigXml = None
+ self.HostingEnvironmentConfig = None
+ self.SharedConfigUrl = None
+ self.SharedConfigXml = None
+ self.SharedConfig = None
+ self.CertificatesUrl = None
+ self.CertificatesXml = None
+ self.Certificates = None
+ self.RoleInstanceId = None
+ self.ContainerId = None
+ self.LoadBalancerProbePort = None # integer, ?list of integers
+
+ def Parse(self, xmlText):
+ self.reinitialize()
+ node = xml.dom.minidom.parseString(xmlText).childNodes[0]
+ if node.localName != "GoalState":
+ Error("GoalState.Parse: root not GoalState")
+ return None
+ for a in node.childNodes:
+ if a.nodeType == node.ELEMENT_NODE:
+ if a.localName == "Incarnation":
+ self.Incarnation = GetNodeTextData(a)
+ elif a.localName == "Machine":
+ for b in a.childNodes:
+ if b.nodeType == node.ELEMENT_NODE:
+ if b.localName == "ExpectedState":
+ self.ExpectedState = GetNodeTextData(b)
+ Log("ExpectedState: " + self.ExpectedState)
+ elif b.localName == "LBProbePorts":
+ for c in b.childNodes:
+ if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
+ self.LoadBalancerProbePort = int(GetNodeTextData(c))
+ elif a.localName == "Container":
+ for b in a.childNodes:
+ if b.nodeType == node.ELEMENT_NODE:
+ if b.localName == "ContainerId":
+ self.ContainerId = GetNodeTextData(b)
+ Log("ContainerId: " + self.ContainerId)
+ elif b.localName == "RoleInstanceList":
+ for c in b.childNodes:
+ if c.localName == "RoleInstance":
+ for d in c.childNodes:
+ if d.nodeType == node.ELEMENT_NODE:
+ if d.localName == "InstanceId":
+ self.RoleInstanceId = GetNodeTextData(d)
+ Log("RoleInstanceId: " + self.RoleInstanceId)
+ elif d.localName == "State":
+ pass
+ elif d.localName == "Configuration":
+ for e in d.childNodes:
+ if e.nodeType == node.ELEMENT_NODE:
+ if e.localName == "HostingEnvironmentConfig":
+ self.HostingEnvironmentConfigUrl = GetNodeTextData(e)
+ LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl)
+ self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
+ self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
+ elif e.localName == "SharedConfig":
+ self.SharedConfigUrl = GetNodeTextData(e)
+ LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
+ self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
+ self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
+ elif e.localName == "Certificates":
+ self.CertificatesUrl = GetNodeTextData(e)
+ LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl)
+ self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
+ self.Certificates = Certificates().Parse(self.CertificatesXml)
+ if self.Incarnation == None:
+ Error("GoalState.Parse: Incarnation missing")
+ return None
+ if self.ExpectedState == None:
+ Error("GoalState.Parse: ExpectedState missing")
+ return None
+ if self.RoleInstanceId == None:
+ Error("GoalState.Parse: RoleInstanceId missing")
+ return None
+ if self.ContainerId == None:
+ Error("GoalState.Parse: ContainerId missing")
+ return None
+ SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
+ return self
+
+ def Process(self):
+ self.HostingEnvironmentConfig.Process()
+
+class OvfEnv(object):
+#
+# <?xml version="1.0" encoding="utf-8"?>
+# <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">
+# <wa:ProvisioningSection>
+# <wa:Version>1.0</wa:Version>
+# <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+# <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
+# <HostName>HostName</HostName>
+# <UserName>UserName</UserName>
+# <UserPassword>UserPassword</UserPassword>
+# <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
+# <SSH>
+# <PublicKeys>
+# <PublicKey>
+# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
+# <Path>$HOME/UserName/.ssh/authorized_keys</Path>
+# </PublicKey>
+# </PublicKeys>
+# <KeyPairs>
+# <KeyPair>
+# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
+# <Path>$HOME/UserName/.ssh/id_rsa</Path>
+# </KeyPair>
+# </KeyPairs>
+# </SSH>
+# </LinuxProvisioningConfigurationSet>
+# </wa:ProvisioningSection>
+# </Environment>
+#
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ self.WaNs = "http://schemas.microsoft.com/windowsazure"
+ self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
+ self.MajorVersion = 1
+ self.MinorVersion = 0
+ self.ComputerName = None
+ self.AdminPassword = None
+ self.UserName = None
+ self.UserPassword = None
+ self.DisableSshPasswordAuthentication = True
+ self.SshPublicKeys = []
+ self.SshKeyPairs = []
+
+ def Parse(self, xmlText):
+ self.reinitialize()
+ dom = xml.dom.minidom.parseString(xmlText)
+ if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
+ Error("Unable to parse OVF XML.")
+ section = None
+ newer = False
+ for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
+ for n in p.childNodes:
+ if n.localName == "Version":
+ verparts = GetNodeTextData(n).split('.')
+ major = int(verparts[0])
+ minor = int(verparts[1])
+ if major > self.MajorVersion:
+ newer = True
+ if major != self.MajorVersion:
+ break
+ if minor > self.MinorVersion:
+ newer = True
+ section = p
+ if newer == True:
+ Warn("Newer provisioning configuration detected. Please consider updating waagent.")
+ if section == None:
+ Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
+ return None
+ self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
+ self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
+ try:
+ self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
+ except:
+ pass
+ disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
+ if len(disableSshPass) != 0:
+ self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true")
+ for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"):
+ fp = None
+ path = None
+ for c in pkey.childNodes:
+ if c.localName == "Fingerprint":
+ fp = GetNodeTextData(c).upper()
+ if c.localName == "Path":
+ path = GetNodeTextData(c)
+ self.SshPublicKeys += [[fp, path]]
+ for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
+ fp = None
+ path = None
+ for c in keyp.childNodes:
+ if c.localName == "Fingerprint":
+ fp = GetNodeTextData(c).upper()
+ if c.localName == "Path":
+ path = GetNodeTextData(c)
+ self.SshKeyPairs += [[fp, path]]
+ return self
+
+ def PrepareDir(self, filepath):
+ home = GetHome()
+ # Expand HOME variable if present in path
+ path = os.path.normpath(filepath.replace("$HOME", home))
+ if (path.startswith("/") == False) or (path.endswith("/") == True):
+ return None
+ dir = path.rsplit('/', 1)[0]
+ if dir != "":
+ CreateDir(dir, "root", 0700)
+ if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
+ ChangeOwner(dir, self.UserName)
+ return path
+
+ def NumberToBytes(self, i):
+ result = []
+ while i:
+ result.append(chr(i&0xFF))
+ i >>= 8
+ result.reverse()
+ return ''.join(result)
+
+ def BitsToString(self, a):
+ index=7
+ s = ""
+ c = 0
+ for bit in a:
+ c = c | (bit << index)
+ index = index - 1
+ if index == -1:
+ s = s + struct.pack('>B', c)
+ c = 0
+ index = 7
+ return s
+
+ def OpensslToSsh(self, file):
+ from pyasn1.codec.der import decoder as der_decoder
+ try:
+ f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
+ k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
+ n=k[0]
+ e=k[1]
+ keydata=""
+ keydata += struct.pack('>I',len("ssh-rsa"))
+ keydata += "ssh-rsa"
+ keydata += struct.pack('>I',len(self.NumberToBytes(e)))
+ keydata += self.NumberToBytes(e)
+ keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
+ keydata += "\0"
+ keydata += self.NumberToBytes(n)
+ except Exception, e:
+ print("OpensslToSsh: Exception " + str(e))
+ return None
+ return "ssh-rsa " + base64.b64encode(keydata) + "\n"
+
+ def Process(self):
+ error = None
+ WaAgent.EnvMonitor.SetHostName(self.ComputerName)
+ if self.DisableSshPasswordAuthentication:
+ filepath = "/etc/ssh/sshd_config"
+ # Disable RFC 4252 and RFC 4256 authentication schemes.
+ ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
+ (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
+ GetFileContents(filepath).split('\n'))) + "PasswordAuthentication no\nChallengeResponseAuthentication no\n")
+ Log("Disabled SSH password-based authentication methods.")
+ if self.AdminPassword != None:
+ os.popen("chpasswd", "w").write("root:" + self.AdminPassword + "\n")
+ if self.UserName != None:
+ error = CreateAccount(self.UserName, self.UserPassword, None, None)
+ sel = os.popen("getenforce").read().startswith("Enforcing")
+ if sel == True and IsRedHat():
+ Run("setenforce 0")
+ home = GetHome()
+ for pkey in self.SshPublicKeys:
+ if not os.path.isfile(pkey[0] + ".crt"):
+ Error("PublicKey not found: " + pkey[0])
+ error = "Failed to deploy public key (0x09)."
+ continue
+ path = self.PrepareDir(pkey[1])
+ if path == None:
+ Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
+ error = "Invalid path for public key (0x03)."
+ continue
+ Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
+ if IsRedHat():
+ Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + pkey[0] + ".pub")
+ if IsUbuntu():
+ # Only supported in new SSH releases
+ Run("ssh-keygen -i -m PKCS8 -f " + pkey[0] + ".pub >> " + path)
+ else:
+ SshPubKey = self.OpensslToSsh(pkey[0] + ".pub")
+ if SshPubKey != None:
+ AppendFileContents(path, SshPubKey)
+ else:
+ Error("Failed: " + pkey[0] + ".crt -> " + path)
+ error = "Failed to deploy public key (0x04)."
+ if IsRedHat():
+ Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
+ if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
+ ChangeOwner(path, self.UserName)
+ for keyp in self.SshKeyPairs:
+ if not os.path.isfile(keyp[0] + ".prv"):
+ Error("KeyPair not found: " + keyp[0])
+ error = "Failed to deploy key pair (0x0A)."
+ continue
+ path = self.PrepareDir(keyp[1])
+ if path == None:
+ Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
+ error = "Invalid path for key pair (0x05)."
+ continue
+ SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
+ os.chmod(path, 0600)
+ Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
+ if IsRedHat():
+ Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path)
+ Run("chcon unconfined_u:object_r:ssh_home_t:s0 " + path + ".pub")
+ if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
+ ChangeOwner(path, self.UserName)
+ ChangeOwner(path + ".pub", self.UserName)
+ if sel == True and IsRedHat():
+ Run("setenforce 1")
+ while not WaAgent.EnvMonitor.IsNamePublished():
+ time.sleep(1)
+ ReloadSshd()
+ return error
+
+def UpdateAndPublishHostNameCommon(name):
+ # RedHat
+ if IsRedHat():
+ filepath = "/etc/sysconfig/network"
+ if os.path.isfile(filepath):
+ ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n"
+ + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n'))))
+
+ for ethernetInterface in PossibleEthernetInterfaces:
+ filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
+ if os.path.isfile(filepath):
+ ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
+ + "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
+
+ # Debian
+ if IsDebian():
+ SetFileContents("/etc/hostname", name)
+
+ for filepath in EtcDhcpClientConfFiles:
+ if os.path.isfile(filepath):
+ ReplaceFileContentsAtomic(filepath, "send host-name \"" + name + "\";\n"
+ + "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents(filepath).split('\n'))))
+
+ # Suse
+ if IsSuse():
+ SetFileContents("/etc/HOSTNAME", name)
+
+class Agent(Util):
+ def __init__(self):
+ self.GoalState = None
+ self.Endpoint = None
+ self.LoadBalancerProbeServer = None
+ self.HealthReportCounter = 0
+ self.TransportCert = ""
+ self.EnvMonitor = None
+ self.SendData = None
+ self.DhcpResponse = None
+
+ def CheckVersions(self):
+ #<?xml version="1.0" encoding="utf-8"?>
+ #<Versions>
+ # <Preferred>
+ # <Version>2010-12-15</Version>
+ # </Preferred>
+ # <Supported>
+ # <Version>2010-12-15</Version>
+ # <Version>2010-28-10</Version>
+ # </Supported>
+ #</Versions>
+ global ProtocolVersion
+ protocolVersionSeen = False
+ node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
+ if node.localName != "Versions":
+ Error("CheckVersions: root not Versions")
+ return False
+ for a in node.childNodes:
+ if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
+ for b in a.childNodes:
+ if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
+ v = GetNodeTextData(b)
+ LogIfVerbose("Fabric supported wire protocol version: " + v)
+ if v == ProtocolVersion:
+ protocolVersionSeen = True
+ if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
+ v = GetNodeTextData(a.getElementsByTagName("Version")[0])
+ LogIfVerbose("Fabric preferred wire protocol version: " + v)
+ if ProtocolVersion < v:
+ Warn("Newer wire protocol version detected. Please consider updating waagent.")
+ if not protocolVersionSeen:
+ Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
+ ProtocolVersion = "2011-08-31"
+ Log("Negotiated wire protocol version: " + ProtocolVersion)
+ return True
+
+ def Unpack(self, buffer, offset, range):
+ result = 0
+ for i in range:
+ result = (result << 8) | Ord(buffer[offset + i])
+ return result
+
+ def UnpackLittleEndian(self, buffer, offset, length):
+ return self.Unpack(buffer, offset, range(length - 1, -1, -1))
+
+ def UnpackBigEndian(self, buffer, offset, length):
+ return self.Unpack(buffer, offset, range(0, length))
+
+ def HexDump3(self, buffer, offset, length):
+ return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
+
+ def HexDump2(self, buffer):
+ return self.HexDump3(buffer, 0, len(buffer))
+
+ def BuildDhcpRequest(self):
+ #
+ # typedef struct _DHCP {
+ # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
+ # UINT8 HardwareAddressType; /* htype: ethernet */
+ # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
+ # UINT8 Hops; /* hops: 0 */
+ # UINT8 TransactionID[4]; /* xid: random */
+ # UINT8 Seconds[2]; /* secs: 0 */
+ # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
+ # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
+ # UINT8 YourIpAddress[4]; /* yiaddr: 0 */
+ # UINT8 ServerIpAddress[4]; /* siaddr: 0 */
+ # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
+ # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */
+ # UINT8 ServerName[64]; /* sname: 0 */
+ # UINT8 BootFileName[128]; /* file: 0 */
+ # UINT8 MagicCookie[4]; /* 99 130 83 99 */
+ # /* 0x63 0x82 0x53 0x63 */
+ # /* options -- hard code ours */
+ #
+ # UINT8 MessageTypeCode; /* 53 */
+ # UINT8 MessageTypeLength; /* 1 */
+ # UINT8 MessageType; /* 1 for DISCOVER */
+ # UINT8 End; /* 255 */
+ # } DHCP;
+ #
+
+ # tuple of 244 zeros
+ # (struct.pack_into would be good here, but requires Python 2.5)
+ sendData = [0] * 244
+
+ transactionID = os.urandom(4)
+ macAddress = GetMacAddress()
+
+ # Opcode = 1
+ # HardwareAddressType = 1 (ethernet/MAC)
+ # HardwareAddressLength = 6 (ethernet/MAC/48 bits)
+ for a in range(0, 3):
+ sendData[a] = [1, 1, 6][a]
+
+ # fill in transaction id (random number to ensure response matches request)
+ for a in range(0, 4):
+ sendData[4 + a] = Ord(transactionID[a])
+
+ LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
+
+ # fill in ClientHardwareAddress
+ for a in range(0, 6):
+ sendData[0x1C + a] = Ord(macAddress[a])
+
+ # DHCP Magic Cookie: 99, 130, 83, 99
+ # MessageTypeCode = 53 DHCP Message Type
+ # MessageTypeLength = 1
+ # MessageType = DHCPDISCOVER
+ # End = 255 DHCP_END
+ for a in range(0, 8):
+ sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
+ return array.array("c", map(chr, sendData))
+
+ def IntegerToIpAddressV4String(self, a):
+ return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
+
+ def RouteAdd(self, net, mask, gateway):
+ if IsWindows():
+ return
+ net = self.IntegerToIpAddressV4String(net)
+ mask = self.IntegerToIpAddressV4String(mask)
+ gateway = self.IntegerToIpAddressV4String(gateway)
+ Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway)
+
+ def HandleDhcpResponse(self, sendData, receiveBuffer):
+ LogIfVerbose("HandleDhcpResponse")
+ bytesReceived = len(receiveBuffer)
+ if bytesReceived < 0xF6:
+ Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
+ return None
+
+ LogIfVerbose("BytesReceived: " + hex(bytesReceived))
+ LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
+
+ # check transactionId, cookie, MAC address
+ # cookie should never mismatch
+ # transactionId and MAC address may mismatch if we see a response meant from another machine
+
+ for offsets in [range(4, 4 + 4), range(0x1C, 0x1C + 6), range(0xEC, 0xEC + 4)]:
+ for offset in offsets:
+ sentByte = Ord(sendData[offset])
+ receivedByte = Ord(receiveBuffer[offset])
+ if sentByte != receivedByte:
+ LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
+ LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
+ LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
+ LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
+ LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
+ LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
+ LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
+ return None
+ endpoint = None
+
+ #
+ # Walk all the returned options, parsing out what we need, ignoring the others.
+ # We need the custom option 245 to find the the endpoint we talk to,
+ # as well as, to handle some Linux DHCP client incompatibilities,
+ # options 3 for default gateway and 249 for routes. And 255 is end.
+ #
+
+ i = 0xF0 # offset to first option
+ while i < bytesReceived:
+ option = Ord(receiveBuffer[i])
+ length = 0
+ if (i + 1) < bytesReceived:
+ length = Ord(receiveBuffer[i + 1])
+ LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
+ if option == 255:
+ LogIfVerbose("DHCP packet ended at offset " + hex(i))
+ break
+ elif option == 249:
+ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+ LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
+ if length < 5:
+ Error("Data too small for option " + option)
+ j = i + 2
+ while j < (i + length + 2):
+ maskLengthBits = Ord(receiveBuffer[j])
+ maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
+ mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
+ j += 1
+ net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
+ net <<= (32 - maskLengthBytes * 8)
+ net &= mask
+ j += maskLengthBytes
+ gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
+ j += 4
+ self.RouteAdd(net, mask, gateway)
+ if j != (i + length + 2):
+ Error("HandleDhcpResponse: Unable to parse routes")
+ elif option == 3 or option == 245:
+ if i + 5 < bytesReceived:
+ if length != 4:
+ Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
+ return None
+ gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
+ IpAddress = self.IntegerToIpAddressV4String(gateway)
+ if option == 3:
+ self.RouteAdd(0, 0, gateway)
+ name = "DefaultGateway"
+ else:
+ endpoint = IpAddress
+ name = "Windows Azure wire protocol endpoint"
+ LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
+ else:
+ Error("HandleDhcpResponse: Data too small for option " + option)
+ else:
+ LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
+ i += length + 2
+ return endpoint
+
+ def DoDhcpWork(self):
+ #
+ # Discover the wire server via DHCP option 245.
+ # And workaround incompatibility with Windows Azure DHCP servers.
+ #
+ ShortSleep = False # Sleep 1 second before retrying DHCP queries.
+
+ if not IsWindows():
+ Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT")
+ Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT")
+
+ sleepDurations = [0, 5, 10, 30, 60, 60, 60, 60]
+ maxRetry = len(sleepDurations)
+ lastTry = (maxRetry - 1)
+ for retry in range(0, maxRetry):
+ try:
+ strRetry = str(retry)
+ prefix = "DoDhcpWork: try=" + strRetry
+ LogIfVerbose(prefix)
+ sendData = self.BuildDhcpRequest()
+ LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if IsSuse():
+ # This is required because sending after binding to 0.0.0.0 fails with
+ # network unreachable when the default gateway is not set up.
+ sock.bind((GetIpv4Address(), 68))
+ else:
+ sock.bind(("0.0.0.0", 68))
+ sock.sendto(sendData, ("<broadcast>", 67))
+ receiveBuffer = sock.recv(1024)
+ sock.close()
+ endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
+ if endpoint == None:
+ LogIfVerbose("DoDhcpWork: No endpoint found")
+ if endpoint != None or retry == lastTry:
+ if endpoint != None:
+ self.SendData = sendData
+ self.DhcpResponse = receiveBuffer
+ if retry == lastTry:
+ LogIfVerbose("DoDhcpWork: try=" + strRetry)
+ return endpoint
+ sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
+ LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
+ time.sleep(sleepDuration)
+ except Exception, e:
+ ErrorWithPrefix(prefix, str(e))
+ ErrorWithPrefix(prefix, traceback.format_exc())
+ return None
+
+ def UpdateAndPublishHostName(self, name):
+ # Set hostname locally and publish to iDNS
+ Log("Setting host name: " + name)
+ UpdateAndPublishHostNameCommon(name)
+ for ethernetInterface in PossibleEthernetInterfaces:
+ if IsSuse():
+ Run("ifrenew " + ethernetInterface)
+ else:
+ Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface)
+ self.RestoreRoutes()
+
+ def RestoreRoutes(self):
+ if self.SendData != None and self.DhcpResponse != None:
+ self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
+
+ def UpdateGoalState(self):
+ goalStateXml = None
+ maxRetry = 9
+ log = NoLog
+ for retry in range(1, maxRetry + 1):
+ strRetry = str(retry)
+ log("retry UpdateGoalState,retry=" + strRetry)
+ goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
+ if goalStateXml != None:
+ break
+ log = Log
+ time.sleep(retry)
+ if not goalStateXml:
+ Error("UpdateGoalState failed.")
+ return
+ Log("Retrieved GoalState from Windows Azure Fabric.")
+ self.GoalState = GoalState(self).Parse(goalStateXml)
+ return self.GoalState
+
+ def ReportReady(self):
+ counter = (self.HealthReportCounter + 1) % 1000000
+ self.HealthReportCounter = counter
+ healthReport = ("<?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>"
+ + self.GoalState.Incarnation
+ + "</GoalStateIncarnation><Container><ContainerId>"
+ + self.GoalState.ContainerId
+ + "</ContainerId><RoleInstanceList><Role><InstanceId>"
+ + self.GoalState.RoleInstanceId
+ + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>")
+ a = self.HttpPost("/machine?comp=health", healthReport)
+ if a != None:
+ return a.getheader("x-ms-latest-goal-state-incarnation-number")
+ return None
+
+ def ReportNotReady(self, status, desc):
+ healthReport = ("<?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>"
+ + self.GoalState.Incarnation
+ + "</GoalStateIncarnation><Container><ContainerId>"
+ + self.GoalState.ContainerId
+ + "</ContainerId><RoleInstanceList><Role><InstanceId>"
+ + self.GoalState.RoleInstanceId
+ + "</InstanceId><Health><State>NotReady</State>"
+ + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>"
+ + "</Health></Role></RoleInstanceList></Container></Health>")
+ a = self.HttpPost("/machine?comp=health", healthReport)
+ if a != None:
+ return a.getheader("x-ms-latest-goal-state-incarnation-number")
+ return None
+
+ def ReportRoleProperties(self, thumbprint):
+ roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>"
+ + "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>"
+ + "<RoleInstances><RoleInstance>"
+ + "<Id>" + self.GoalState.RoleInstanceId + "</Id>"
+ + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>"
+ + "</RoleInstance></RoleInstances></Container></RoleProperties>")
+ a = self.HttpPost("/machine?comp=roleProperties", roleProperties)
+ Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
+ return a
+
+ def LoadBalancerProbeServer_Shutdown(self):
+ if self.LoadBalancerProbeServer != None:
+ self.LoadBalancerProbeServer.shutdown()
+ self.LoadBalancerProbeServer = None
+
+ def GenerateTransportCert(self):
+ Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
+ cert = ""
+ for line in GetFileContents("TransportCert.pem").split('\n'):
+ if not "CERTIFICATE" in line:
+ cert += line.rstrip()
+ return cert
+
+ def Provision(self):
+ if IsWindows():
+ Log("Skipping Provision on Windows")
+ return
+ enabled = Config.get("Provisioning.Enabled")
+ if enabled != None and enabled.lower().startswith("n"):
+ return
+ Log("Provisioning image started.")
+ type = Config.get("Provisioning.SshHostKeyPairType")
+ if type == None:
+ type = "rsa"
+ regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
+ if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
+ Run("rm -f /etc/ssh/ssh_host_*key*")
+ Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key")
+ ReloadSshd()
+ SetFileContents(LibDir + "/provisioned", "")
+ dvd = "/dev/hdc"
+ if os.path.exists("/dev/sr0"):
+ dvd = "/dev/sr0"
+ if Run("fdisk -l " + dvd + " | grep Disk"):
+ return
+ CreateDir("/mnt/cdrom/secure", "root", 0700)
+ if Run("mount " + dvd + " /mnt/cdrom/secure"):
+ Error("Unable to provision: Failed to mount DVD.")
+ return "Failed to retrieve provisioning data (0x01)."
+ if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
+ Error("Unable to provision: Missing ovf-env.xml on DVD.")
+ return "Failed to retrieve provisioning data (0x02)."
+ ovfxml = GetFileContents("/mnt/cdrom/secure/ovf-env.xml")
+ SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml))
+ Run("umount /mnt/cdrom/secure")
+ error = None
+ if ovfxml != None:
+ Log("Provisioning image using OVF settings in the DVD.")
+ ovfobj = OvfEnv().Parse(ovfxml)
+ if ovfobj != None:
+ error = ovfobj.Process()
+ # This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml
+ fingerprint = os.popen("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub").read().rstrip().split()[1].replace(':','')
+ self.ReportRoleProperties(fingerprint)
+ delRootPass = Config.get("Provisioning.DeleteRootPassword")
+ if delRootPass != None and delRootPass.lower().startswith("y"):
+ DeleteRootPassword()
+ Log("Provisioning image completed.")
+ return error
+
+ def Run(self):
+ if IsLinux():
+ SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
+
+ if GetIpv4Address() == None:
+ Log("Waiting for network.")
+ while(GetIpv4Address() == None):
+ time.sleep(10)
+
+ Log("IPv4 address: " + GetIpv4Address())
+ Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in GetMacAddress()]))
+
+ # Consume Entropy in ACPI table provided by Hyper-V
+ try:
+ SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
+ except:
+ pass
+
+ Log("Probing for Windows Azure environment.")
+ self.Endpoint = self.DoDhcpWork()
+
+ if self.Endpoint == None:
+ Log("Windows Azure environment not detected.")
+ while True:
+ time.sleep(60)
+
+ Log("Discovered Windows Azure endpoint: " + self.Endpoint)
+ if not self.CheckVersions():
+ Error("Agent.CheckVersions failed")
+ sys.exit(1)
+
+ self.EnvMonitor = EnvMonitor()
+
+ # Set SCSI timeout on root device
+ try:
+ timeout = Config.get("OS.RootDeviceScsiTimeout")
+ if timeout != None:
+ SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout)
+ except:
+ pass
+
+ global Openssl
+ Openssl = Config.get("OS.OpensslPath")
+ if Openssl == None:
+ Openssl = "openssl"
+
+ self.TransportCert = self.GenerateTransportCert()
+
+ incarnation = None # goalStateIncarnationFromHealthReport
+ currentPort = None # loadBalancerProbePort
+ goalState = None # self.GoalState, instance of GoalState
+ provisioned = os.path.exists(LibDir + "/provisioned")
+ program = Config.get("Role.StateConsumer")
+ provisionError = None
+ while True:
+ if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
+ goalState = self.UpdateGoalState()
+
+ if provisioned == False:
+ self.ReportNotReady("Provisioning", "Starting")
+
+ goalState.Process()
+
+ if provisioned == False:
+ provisionError = self.Provision()
+ provisioned = True
+
+ #
+ # only one port supported
+ # restart server if new port is different than old port
+ # stop server if no longer a port
+ #
+ goalPort = goalState.LoadBalancerProbePort
+ if currentPort != goalPort:
+ self.LoadBalancerProbeServer_Shutdown()
+ currentPort = goalPort
+ if currentPort != None:
+ self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)
+
+ if program != None and DiskActivated == True:
+ os.spawnl(os.P_NOWAIT, program, program, "Ready")
+ program = None
+
+ if goalState.ExpectedState == "Stopped":
+ program = Config.get("Role.StateConsumer")
+ if program != None:
+ Run(program + " Shutdown")
+ self.EnvMonitor.shutdown()
+ self.LoadBalancerProbeServer_Shutdown()
+ command = ["/sbin/shutdown -hP now", "shutdown /s /t 5"][IsWindows()]
+ Run(command)
+ return
+
+ sleepToReduceAccessDenied = 3
+ time.sleep(sleepToReduceAccessDenied)
+ i = None
+ if provisionError != None:
+ i = self.ReportNotReady("ProvisioningFailed", provisionError)
+ else:
+ i = self.ReportReady()
+ if i != None:
+ incarnation = i
+ time.sleep(25 - sleepToReduceAccessDenied)
+
+Init_Suse = """\
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Provides: WindowsAzureLinuxAgent
+# Required-Start: $network sshd
+# Required-Stop: $network sshd
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Description: Start the WindowsAzureLinuxAgent
+### END INIT INFO
+
+WAZD_BIN=/usr/sbin/waagent
+test -x $WAZD_BIN || exit 5
+
+case "$1" in
+ start)
+ echo "Starting WindowsAzureLinuxAgent"
+ ## Start daemon with startproc(8). If this fails
+ ## the echo return value is set appropriate.
+
+ startproc -f $WAZD_BIN -daemon
+ exit $?
+ ;;
+ stop)
+ echo "Shutting down WindowsAzureLinuxAgent"
+ ## Stop daemon with killproc(8) and if this fails
+ ## set echo the echo return value.
+
+ killproc -p /var/run/waagent.pid $WAZD_BIN
+ exit $?
+ ;;
+ try-restart)
+ ## Stop the service and if this succeeds (i.e. the
+ ## service was running before), start it again.
+ $0 status >/dev/null && $0 restart
+ ;;
+ restart)
+ ## Stop the service and regardless of whether it was
+ ## running or not, start it again.
+ $0 stop
+ $0 start
+ ;;
+ force-reload|reload)
+ ;;
+ status)
+ echo -n "Checking for service WindowsAzureLinuxAgent "
+ ## Check status with checkproc(8), if process is running
+ ## checkproc will return with exit status 0.
+
+ checkproc -p $WAZD_PIDFILE $WAZD_BIN
+ exit $?
+ ;;
+ probe)
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
+ exit 1
+ ;;
+esac
+"""
+
+Init_RedHat = """\
+#!/bin/bash
+#
+# Init file for WindowsAzureLinuxAgent.
+#
+# chkconfig: 2345 60 80
+# description: WindowsAzureLinuxAgent
+#
+
+# source function library
+. /etc/rc.d/init.d/functions
+
+RETVAL=0
+FriendlyName="WindowsAzureLinuxAgent"
+WAZD_BIN=/usr/sbin/waagent
+
+start()
+{
+ echo -n $"Starting $FriendlyName: "
+ $WAZD_BIN -daemon &
+}
+
+stop()
+{
+ echo -n $"Stopping $FriendlyName: "
+ killproc -p /var/run/waagent.pid $WAZD_BIN
+ RETVAL=$?
+ echo
+ return $RETVAL
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ reload)
+ ;;
+ report)
+ ;;
+ status)
+ status $WAZD_BIN
+ RETVAL=$?
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|restart|status}"
+ RETVAL=1
+esac
+exit $RETVAL
+"""
+
+Init_Debian = """\
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: WindowsAzureLinuxAgent
+# Required-Start: $network $syslog
+# Required-Stop: $network $syslog
+# Should-Start: $network $syslog
+# Should-Stop: $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: WindowsAzureLinuxAgent
+# Description: WindowsAzureLinuxAgent
+### END INIT INFO
+
+. /lib/lsb/init-functions
+
+OPTIONS="-daemon"
+WAZD_BIN=/usr/sbin/waagent
+WAZD_PID=/var/run/waagent.pid
+
+case "$1" in
+ start)
+ log_begin_msg "Starting WindowsAzureLinuxAgent..."
+ pid=$( pidofproc $WAZD_BIN )
+ if [ -n "$pid" ] ; then
+ log_begin_msg "Already running."
+ log_end_msg 0
+ exit 0
+ fi
+ start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
+ log_end_msg $?
+ ;;
+
+ stop)
+ log_begin_msg "Stopping WindowsAzureLinuxAgent..."
+ start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
+ ret=$?
+ rm -f $WAZD_PID
+ log_end_msg $ret
+ ;;
+ force-reload)
+ $0 restart
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ status)
+ status_of_proc $WAZD_BIN && exit 0 || exit $?
+ ;;
+ *)
+ log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
+ exit 1
+ ;;
+esac
+
+exit 0
+"""
+
+WaagentConf = """\
+#
+# Windows Azure Linux Agent Configuration
+#
+
+Role.StateConsumer=None # Specified program is invoked with "Ready" or "Shutdown".
+ # Shutdown will be initiated only after the program returns. Windows Azure will
+ # power off the VM if shutdown is not completed within ?? minutes.
+Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
+Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
+
+Provisioning.Enabled=y #
+Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
+Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
+Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
+Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
+
+ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
+ResourceDisk.Filesystem=ext4 #
+ResourceDisk.MountPoint=/mnt/resource #
+ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
+ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
+
+LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
+
+Logs.Verbose=n #
+
+OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
+OS.OpensslPath=None # If "None", the system default version is used.
+"""
+
+WaagentLogrotate = """\
+/var/log/waagent.log {
+ monthly
+ rotate 6
+ notifempty
+ missingok
+}
+"""
+
+def AddToLinuxKernelCmdline(options):
+ if os.path.isfile("/boot/grub/menu.lst"):
+ Run("sed -i --follow-symlinks '/kernel/s|$| " + options + " |' /boot/grub/menu.lst")
+ filepath = "/etc/default/grub"
+ if os.path.isfile(filepath):
+ filecontents = GetFileContents(filepath).split('\n')
+ current = filter(lambda a: a.startswith("GRUB_CMDLINE_LINUX"), filecontents)
+ ReplaceFileContentsAtomic(filepath,
+ "\n".join(filter(lambda a: not a.startswith("GRUB_CMDLINE_LINUX"), filecontents))
+ + current[0][:-1] + " " + options + "\"\n")
+ Run("update-grub")
+
+def ApplyVNUMAWorkaround():
+ VersionParts = platform.release().replace('-', '.').split('.')
+ if int(VersionParts[0]) > 2:
+ return
+ if int(VersionParts[1]) > 6:
+ return
+ if int(VersionParts[2]) > 37:
+ return
+ AddToLinuxKernelCmdline("numa=off")
+ # TODO: This is not ideal for offline installation.
+ print("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
+
+def RevertVNUMAWorkaround():
+ print("Automatic reverting of GRUB configuration is not yet supported. Please edit by hand.")
+
+def Install():
+ if IsWindows():
+ print("ERROR: -install invalid for Windows.")
+ return 1
+ os.chmod(sys.argv[0], 0755)
+ SwitchCwd()
+ requiredDeps = [ "/sbin/route", "/sbin/shutdown" ]
+ if IsDebian():
+ requiredDeps += [ "/usr/sbin/update-rc.d" ]
+ if IsSuse():
+ requiredDeps += [ "/sbin/insserv" ]
+ for a in requiredDeps:
+ if not os.path.isfile(a):
+ Error("Missing required dependency: " + a)
+ return 1
+ missing = False
+ for a in [ "ssh-keygen", "useradd", "openssl", "sfdisk",
+ "fdisk", "mkfs", "chpasswd", "sed", "grep", "sudo" ]:
+ if Run("which " + a + " > /dev/null 2>&1"):
+ Warn("Missing dependency: " + a)
+ missing = True
+ if missing == True:
+ Warn("Please resolve missing dependencies listed for full functionality.")
+ if UsesRpm():
+ if not Run("rpm --quiet -q NetworkManager"):
+ Error(GuestAgentLongName + " is not compatible with NetworkManager.")
+ return 1
+ if Run("rpm --quiet -q python-pyasn1"):
+ Error(GuestAgentLongName + " requires python-pyasn1.")
+ return 1
+ if UsesDpkg() and Run("dpkg -l network-manager | grep -q ^un"):
+ Error(GuestAgentLongName + " is not compatible with network-manager.")
+ return 1
+ for a in RulesFiles:
+ if os.path.isfile(a):
+ if os.path.isfile(GetLastPathElement(a)):
+ os.remove(GetLastPathElement(a))
+ shutil.move(a, ".")
+ Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
+ filename = "waagent"
+ filepath = "/etc/init.d/" + filename
+ distro = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
+ if distro == 0:
+ Error("Unable to detect Linux Distribution.")
+ return 1
+ init = [[Init_RedHat, "chkconfig --add " + filename],
+ [Init_Debian, "update-rc.d " + filename + " defaults"],
+ [Init_Suse, "insserv " + filename]][distro - 1]
+ SetFileContents(filepath, init[0])
+ os.chmod(filepath, 0755)
+ Run(init[1])
+ if os.path.isfile("/etc/waagent.conf"):
+ try:
+ os.remove("/etc/waagent.conf.old")
+ except:
+ pass
+ try:
+ os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
+ Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
+ except:
+ pass
+ SetFileContents("/etc/waagent.conf", WaagentConf)
+ SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
+ filepath = "/etc/ssh/sshd_config"
+ ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
+ a.startswith("ClientAliveInterval"),
+ GetFileContents(filepath).split('\n'))) + "ClientAliveInterval 180\n")
+ Log("Configured SSH client probing to keep connections alive.")
+ ApplyVNUMAWorkaround()
+ return 0
+
+def Uninstall():
+ if IsWindows():
+ print("ERROR: -uninstall invalid for windows, see waagent_service.exe")
+ return 1
+ SwitchCwd()
+ for a in RulesFiles:
+ if os.path.isfile(GetLastPathElement(a)):
+ try:
+ shutil.move(GetLastPathElement(a), a)
+ Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
+ except:
+ pass
+ filename = "waagent"
+ a = IsRedHat() + IsDebian() * 2 + IsSuse() * 3
+ if a == 0:
+ Error("Unable to detect Linux Distribution.")
+ return 1
+ Run("service " + filename + " stop")
+ cmd = ["chkconfig --del " + filename,
+ "update-rc.d -f " + filename + " remove",
+ "insserv -r " + filename][a - 1]
+ Run(cmd)
+ for f in os.listdir(LibDir) + ["/etc/init.d/" + filename, "/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]:
+ try:
+ os.remove(f)
+ except:
+ pass
+ RevertVNUMAWorkaround()
+ return 0
+
+def DeleteRootPassword():
+ SetFileContents("/etc/shadow-temp", "")
+ os.chmod("/etc/shadow-temp", 0000)
+ Run("(echo root:*LOCK*:14600:::::: && grep -v ^root /etc/shadow ) > /etc/shadow-temp")
+ Run("mv -f /etc/shadow-temp /etc/shadow")
+ Log("Root password deleted.")
+
+def Deprovision(force, deluser):
+ if IsWindows():
+ Run(os.environ["windir"] + "\\system32\\sysprep\\sysprep.exe /generalize")
+ return 0
+
+ SwitchCwd()
+ ovfxml = GetFileContents("ovf-env.xml")
+ ovfobj = None
+ if ovfxml != None:
+ ovfobj = OvfEnv().Parse(ovfxml)
+
+ print("WARNING! The waagent service will be stopped.")
+ print("WARNING! All SSH host key pairs will be deleted.")
+ print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
+ print("WARNING! Cached DHCP leases will be deleted.")
+
+ delRootPass = Config.get("Provisioning.DeleteRootPassword")
+ if delRootPass != None and delRootPass.lower().startswith("y"):
+ print("WARNING! root password will be disabled. You will not be able to login as root.")
+
+ if ovfobj != None and deluser == True:
+ print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
+
+ if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
+ return 1
+
+ Run("service waagent stop")
+
+ if deluser == True:
+ DeleteAccount(ovfobj.UserName)
+
+ # Remove SSH host keys
+ regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
+ if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
+ Run("rm -f /etc/ssh/ssh_host_*key*")
+
+ # Remove root password
+ if delRootPass != None and delRootPass.lower().startswith("y"):
+ DeleteRootPassword()
+
+ # Remove distribution specific networking configuration
+
+ UpdateAndPublishHostNameCommon("localhost.localdomain")
+
+ # RedHat, Suse, Debian
+ for a in VarLibDhcpDirectories:
+ Run("rm -f " + a + "/*")
+
+ # Clear LibDir, remove nameserver and root bash history
+ for f in os.listdir(LibDir) + ["/etc/resolv.conf", "/root/.bash_history", "/var/log/waagent.log"]:
+ try:
+ os.remove(f)
+ except:
+ pass
+
+ return 0
+
+def SwitchCwd():
+ if not IsWindows():
+ CreateDir(LibDir, "root", 0700)
+ os.chdir(LibDir)
+
+def Usage():
+ print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
+ return 0
+
+if GuestAgentVersion == "":
+ print("WARNING! This is a non-standard agent that does not include a valid version string.")
+if IsLinux() and not DetectLinuxDistro():
+ print("WARNING! Unable to detect Linux distribution. Some functionality may be broken.")
+
+if len(sys.argv) == 1:
+ sys.exit(Usage())
+
+args = []
+force = False
+for a in sys.argv[1:]:
+ if re.match("^([-/]*)(help|usage|\?)", a):
+ sys.exit(Usage())
+ elif re.match("^([-/]*)verbose", a):
+ Verbose = True
+ elif re.match("^([-/]*)force", a):
+ force = True
+ elif re.match("^([-/]*)(setup|install)", a):
+ sys.exit(Install())
+ elif re.match("^([-/]*)(uninstall)", a):
+ sys.exit(Uninstall())
+ else:
+ args.append(a)
+
+Config = ConfigurationProvider()
+
+verbose = Config.get("Logs.Verbose")
+if verbose != None and verbose.lower().startswith("y"):
+ Verbose = True
+
+daemon = False
+for a in args:
+ if re.match("^([-/]*)deprovision\+user", a):
+ sys.exit(Deprovision(force, True))
+ elif re.match("^([-/]*)deprovision", a):
+ sys.exit(Deprovision(force, False))
+ elif re.match("^([-/]*)daemon", a):
+ daemon = True
+ elif re.match("^([-/]*)version", a):
+ print(GuestAgentVersion + " running on " + LinuxDistro)
+ sys.exit(0)
+ elif re.match("^([-/]*)serialconsole", a):
+ AddToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
+ Log("Configured kernel to use ttyS0 as the boot console.")
+ sys.exit(0)
+ else:
+ print("Invalid command line parameter:" + a)
+ sys.exit(1)
+
+if daemon == False:
+ sys.exit(Usage())
+
+try:
+ SwitchCwd()
+ Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
+ if IsLinux():
+ Log("Linux Distribution Detected : " + LinuxDistro)
+ WaAgent = Agent()
+ WaAgent.Run()
+except Exception, e:
+ Error(traceback.format_exc())
+ Error("Exception: " + str(e))
+ sys.exit(1)