pax_global_header00006660000000000000000000000064145073726070014525gustar00rootroot0000000000000052 comment=993afaa3ce6cb05d03443cfcf7eb87ae63d39772 guest-agent-20231004.02/000077500000000000000000000000001450737260700144255ustar00rootroot00000000000000guest-agent-20231004.02/.gitignore000066400000000000000000000002121450737260700164100ustar00rootroot00000000000000# ignore all built binaries **/gce_workload_cert_refresh **/google_authorized_keys **/google_guest_agent **/google_metadata_script_runner guest-agent-20231004.02/90-google-guest-agent.preset000066400000000000000000000002261450737260700215740ustar00rootroot00000000000000enable google-guest-agent.service enable google-shutdown-scripts.service enable google-startup-scripts.service enable gce-workload-cert-refresh.timer guest-agent-20231004.02/CONTRIBUTING.md000066400000000000000000000057561450737260700166730ustar00rootroot00000000000000# How to become a contributor and submit your own code ## Before you begin ### Sign our Contributor License Agreement Contributions to this project must be accompanied by a [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project. If you or your current employer have already signed the Google CLA (even if it was for a different project), you probably don't need to do it again. Visit to see your current agreements or to sign a new one. ### Review our community guidelines This project follows [Google's Open Source Community Guidelines](https://opensource.google/conduct/). ## Contributing a patch 1. Submit an issue describing your proposed change to the repo in question. 1. The repo owner will respond to your issue promptly. 1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 1. Fork the desired repo, develop and test your code changes. 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. Refer to the [Google Cloud Platform Samples Style Guide] (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the recommended coding standards for this organization. 1. Ensure that your code has an appropriate set of unit tests which all pass. 1. Submit a pull request. ## Contributing a new sample App 1. Submit an issue to the `GoogleCloudPlatform/Template` repo describing your proposed sample app. 1. The Template repo owner will respond to your enhancement issue promptly. Instructional value is the top priority when evaluating new app proposals for this collection of repos. 1. If your proposal is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 1. Create your own repo for your app following this naming convention: * {product}-{app-name}-{language} * products: appengine, compute, storage, bigquery, prediction, cloudsql * example: appengine-guestbook-python * For multi-product apps, concatenate the primary products, like this: compute-appengine-demo-suite-python. * For multi-language apps, concatenate the primary languages like this: appengine-sockets-python-java-go. 1. Clone the `README.md`, `CONTRIB.md` and `LICENSE` files from the GoogleCloudPlatform/Template repo. 1. Ensure that your code adheres to the existing style in the sample to which you are contributing. Refer to the [Google Cloud Platform Samples Style Guide] (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the recommended coding standards for this organization. 1. Ensure that your code has an appropriate set of unit tests which all pass. 1. Submit a request to fork your repo in GoogleCloudPlatform organization via your proposal issue. guest-agent-20231004.02/LICENSE000066400000000000000000000261341450737260700154400ustar00rootroot00000000000000 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.guest-agent-20231004.02/OWNERS000066400000000000000000000004401450737260700153630ustar00rootroot00000000000000# This file enables automatic assignment of PR reviewers. # See the OWNERS docs at https://go.k8s.io/owners approvers: - a-crate - bkatyl - chaitanyakulkarni28 - dorileo - drewhli - elicriffield - jjerger - karnvadaliya - koln67 - quintonamore - vorakl - zmarano guest-agent-20231004.02/README.md000066400000000000000000000247301450737260700157120ustar00rootroot00000000000000## Guest Agent for Google Compute Engine This repository contains the source code and packaging artifacts for the Google guest agent and metadata script runner binaries. These components are installed on Windows and Linux GCE VMs in order to enable GCE platform features. **Table of Contents** * [Overview](#overview) * [Features](#features) * [Account Management](#account-management) * [Clock Skew](#clock-skew) * [OS Login](#os-login) * [Network](#network) * [Windows Failover Cluster Support](#windows-failover-cluster-support) * [Instance Setup](#instance-setup) * [Telemetry](#telemetry) * [Metadata Scripts](#metadata-scripts) * [Configuration](#configuration) * [Packaging](#packaging) ## Overview The repository contains these components: * **google-guest-agent** daemon which handles all of the areas outlined below in "features" * **google-metadata-script-runner** binary to run user-provided scripts at VM startup and shutdown. ## Features The guest agent functionality can be separated into various areas of responsibility. Historically, on Linux these were managed by separate independent processes, but today they are all managed by the guest agent. The `Daemons` section of the instance configs file on Linux refers to these areas of responsibility. This allows a user to easily modify or disable functionality. Behaviors for each area of responsibility are detailed below. #### Account management On Windows, the agent handles [creating user accounts and setting/resetting passwords.](https://cloud.google.com/compute/docs/instances/windows/creating-passwords-for-windows-instances) On Linux: If OS Login is not used, the guest agent will be responsible for provisioning and deprovisioning user accounts. The agent creates local user accounts and maintains the authorized SSH keys file for each. User account creation is based on [adding and remove SSH Keys](https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys) stored in metadata. The guest agent has the following behaviors: * Administrator permissions are managed with a `google-sudoers` Linux group. Members of this group are granted `sudo` permissions on the VM. * All users provisioned by the account daemon are added to the `google-sudoers` group. * The daemon stores a file in the guest to record which user accounts are managed by Google. * User accounts not managed by Google are not touched by the accounts daemon. * The authorized keys file for a Google managed user is deleted when all SSH keys for the user are removed from metadata. #### OS Login (Linux only) If the user has [configured OS Login via metadata](https://cloud.google.com/compute/docs/instances/managing-instance-access), the guest agent will be responsible for configuring the OS to use OS Login, otherwise called 'enabling' OS Login. This consists of: * Adding a Google config block to the SSHD configuration file and restarting SSHD. * Adding OS Login entries to the nsswitch.conf file. * Adding OS Login entries to the PAM configuration file for SSHD. If the user disables OS login via metadata, the configuration changes will be removed. #### Clock Skew (Linux only) The guest agent is responsible for syncing the software clock with the hypervisor clock after a stop/start event or after a migration. Preventing clock skew may result in `system time has changed` messages in VM logs. #### Network The guest agent uses network interface metadata to manage the network interfaces in the guest by performing the following tasks: * Enabled all associated network interfaces on boot. * Setup or remove IP routes in the guest for IP forwarding and IP aliases * Only IPv4 IP addresses are currently supported. * Routes are set on the primary ethernet interface. * Google routes are configured, by default, with the routing protocol ID `66`. This ID is a namespace for daemon configured IP addresses. It can be changed with the config file, see below. #### Windows Failover Cluster Support (Windows only) The agent can monitor the active node in the Windows Failover Cluster and coordinate with GCP Internal Load Balancer to forward all cluster traffic to the expected node. The following fields on instance metadata or instance\_configs.cfg can control the behavior: * `enable-wsfc`: If set to true, all IP forwarding info will be ignored and agent will start responding to the health check port. Default false. * `wsfc-agent-port`: The port which the agent will respond to health checks. Default 59998. * `wsfc-addrs`: A comma separated list of IP address. This is an advanced setting to enable user have both normal forwarding IPs and cluster IPs on the same instance. If set, agent will only skip-auto configuring IPs in the list. Default empty. #### Instance Setup (Linux only) The guest agent will perform some actions once each time on startup: * Optimize for local SSD. * Enable multi-queue on all the virtionet devices. The guest agent will perform some actions one time only, on the first VM boot: * Generate SSH host keys. * Create the `boto` config for using Google Cloud Storage. #### Telemetry The guest agent will record some basic system telemetry information at start and then once every 24 hours. * Guest agent version and architecture * Operating system name and version * Operating system kernel release and version Telemetry can be disabled by setting the metadata key `disable-guest-telemetry` to `true`. ## Metadata Scripts Metadata scripts implement support for running user provided [startup scripts](https://cloud.google.com/compute/docs/startupscript) and [shutdown scripts](https://cloud.google.com/compute/docs/shutdownscript). The guest support for metadata scripts is implemented in Python with the following design details. * Metadata scripts are executed in a shell. * If multiple metadata keys are specified (e.g. `startup-script` and `startup-script-url`) both are executed. * If multiple metadata keys are specified (e.g. `startup-script` and `startup-script-url`) a URL is executed first. * The exit status of a metadata script is logged after completed execution. ## Configuration Users of Google provided images may configure the guest environment behaviors using a configuration file. To make configuration changes on Windows, follow [these instructions](https://cloud.google.com/compute/docs/instances/windows/creating-managing-windows-instances#configure-windows-features) To make configuration changes on Linux, add settings to `/etc/default/instance_configs.cfg`. If you are attempting to change the behavior of a running instance, restart the guest agent after modifying. Linux distributions looking to include their own defaults can specify settings in `/etc/default/instance_configs.cfg.distro`. These settings will not override `/etc/default/instance_configs.cfg`. This enables distribution settings that do not override user configuration during package update. The following are valid user configuration options. Section | Option | Value ----------------- | ---------------------- | ----- Accounts | deprovision\_remove | `true` makes deprovisioning a user destructive. Accounts | groups | Comma separated list of groups for newly provisioned users. Accounts | useradd\_cmd | Command string to create a new user. Accounts | userdel\_cmd | Command string to delete a user. Accounts | usermod\_cmd | Command string to modify a user's groups. Accounts | gpasswd\_add\_cmd | Command string to add a user to a group. Accounts | gpasswd\_remove\_cmd | Command string to remove a user from a group. Accounts | groupadd\_cmd | Command string to create a new group. Daemons | accounts\_daemon | `false` disables the accounts daemon. Daemons | clock\_skew\_daemon | `false` disables the clock skew daemon. Daemons | network\_daemon | `false` disables the network daemon. InstanceSetup | host\_key\_types | Comma separated list of host key types to generate. InstanceSetup | optimize\_local\_ssd | `false` prevents optimizing for local SSD. InstanceSetup | network\_enabled | `false` skips instance setup functions that require metadata. InstanceSetup | set\_boto\_config | `false` skips setting up a `boto` config. InstanceSetup | set\_host\_keys | `false` skips generating host keys on first boot. InstanceSetup | set\_multiqueue | `false` skips multiqueue driver support. IpForwarding | ethernet\_proto\_id | Protocol ID string for daemon added routes. IpForwarding | ip\_aliases | `false` disables setting up alias IP routes. IpForwarding | target\_instance\_ips | `false` disables internal IP address load balancing. MetadataScripts | default\_shell | String with the default shell to execute scripts. MetadataScripts | run\_dir | String base directory where metadata scripts are executed. MetadataScripts | startup | `false` disables startup script execution. MetadataScripts | shutdown | `false` disables shutdown script execution. NetworkInterfaces | setup | `false` skips network interface setup. NetworkInterfaces | ip\_forwarding | `false` skips IP forwarding. NetworkInterfaces | dhcp\_command | String path for alternate dhcp executable used to enable network interfaces. OSLogin | cert_authentication | `false` prevents guest-agent from setting up sshd's `TrustedUserCAKeys`, `AuthorizedPrincipalsCommand` and `AuthorizedPrincipalsCommandUser` configuration keys. Default value: `true`. Setting `network_enabled` to `false` will disable generating host keys and the `boto` config in the guest. ## Packaging The guest agent and metadata script runner are packaged in DEB, RPM or Googet format packages which are published to Google Cloud repositories and preinstalled on Google managed GCE Images. Packaging scripts for each platform are stored in the packaging/ directory. We build the following packages for the Windows guest environment: google-compute-engine-windows - contains the guest agent executable. google-compute-engine-metadata-scripts - contains files to run startup and shutdown scripts. We build the following packages for the Linux guest environment: google-guest-agent - contains the guest agent and metadata script runner executables, as well as service files for both. guest-agent-20231004.02/THIRD_PARTY_LICENSES/000077500000000000000000000000001450737260700175435ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/000077500000000000000000000000001450737260700227015ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/000077500000000000000000000000001450737260700233065ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/LICENSE000066400000000000000000000261361450737260700243230ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/compute/000077500000000000000000000000001450737260700247625ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/compute/metadata/000077500000000000000000000000001450737260700265425ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/compute/metadata/LICENSE000066400000000000000000000261361450737260700275570ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/logging/000077500000000000000000000000001450737260700247345ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/logging/LICENSE000066400000000000000000000261361450737260700257510ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/longrunning/000077500000000000000000000000001450737260700256465ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/longrunning/LICENSE000066400000000000000000000261361450737260700266630ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/storage/000077500000000000000000000000001450737260700247525ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/cloud.google.com/go/storage/LICENSE000066400000000000000000000261361450737260700257670ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/000077500000000000000000000000001450737260700216025ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/000077500000000000000000000000001450737260700255125ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-agent/000077500000000000000000000000001450737260700277355ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-agent/LICENSE000066400000000000000000000261151450737260700307470ustar00rootroot00000000000000 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 2020 Google Inc. 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. google_guest_agent/000077500000000000000000000000001450737260700335175ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-agentLICENSE000066400000000000000000000261151450737260700345310ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-agent/google_guest_agent 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 2020 Google Inc. 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-logging-go/000077500000000000000000000000001450737260700306705ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-logging-go/logger/000077500000000000000000000000001450737260700321475ustar00rootroot00000000000000LICENSE000066400000000000000000000261351450737260700331040ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/GoogleCloudPlatform/guest-logging-go/logger 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/go-ini/000077500000000000000000000000001450737260700227645ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/go-ini/ini/000077500000000000000000000000001450737260700235435ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/go-ini/ini/LICENSE000066400000000000000000000240151450737260700245520ustar00rootroot00000000000000Apache 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: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and 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 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 2014 Unknwon 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/golang/000077500000000000000000000000001450737260700230515ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/golang/groupcache/000077500000000000000000000000001450737260700251715ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/golang/groupcache/lru/000077500000000000000000000000001450737260700257735ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/golang/groupcache/lru/LICENSE000066400000000000000000000240411450737260700270010ustar00rootroot00000000000000Apache 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: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and 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 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/golang/protobuf/000077500000000000000000000000001450737260700247115ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/golang/protobuf/LICENSE000066400000000000000000000027101450737260700257160ustar00rootroot00000000000000Copyright 2010 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/000077500000000000000000000000001450737260700230565ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-cmp/000077500000000000000000000000001450737260700242405ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-cmp/cmp/000077500000000000000000000000001450737260700250175ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-cmp/cmp/LICENSE000066400000000000000000000027071450737260700260320ustar00rootroot00000000000000Copyright (c) 2017 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-sev-guest/000077500000000000000000000000001450737260700254035ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-sev-guest/LICENSE000066400000000000000000000261361450737260700264200ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-tpm-tools/000077500000000000000000000000001450737260700254175ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-tpm-tools/LICENSE000066400000000000000000000370171450737260700264340ustar00rootroot00000000000000 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. -------------------------------------------------------------------- IBM simulator code (in tpm2-simulator/) uses the following license: -------------------------------------------------------------------- (c) Copyright IBM Corporation 2016. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the names of the IBM Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------- A portion of the source code is derived from the TPM specification, which has a TCG copyright. It is reproduced here for reference. -------------------------------------------------------------------- Licenses and Notices Copyright Licenses: * Trusted Computing Group (TCG) grants to the user of the source code in this specification (the "Source Code") a worldwide, irrevocable, nonexclusive, royalty free, copyright license to reproduce, create derivative works, distribute, display and perform the Source Code and derivative works thereof, and to grant others the rights granted herein. * The TCG grants to the user of the other parts of the specification (other than the Source Code) the rights to reproduce, distribute, display, and perform the specification solely for the purpose of developing products based on such documents. Source Code Distribution Conditions: * Redistributions of Source Code must retain the above copyright licenses, this list of conditions and the following disclaimers. * Redistributions in binary form must reproduce the above copyright licenses, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. Disclaimers: * THE COPYRIGHT LICENSES SET FORTH ABOVE DO NOT REPRESENT ANY FORM OF LICENSE OR WAIVER, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, WITH RESPECT TO PATENT RIGHTS HELD BY TCG MEMBERS (OR OTHER THIRD PARTIES) THAT MAY BE NECESSARY TO IMPLEMENT THIS SPECIFICATION OR OTHERWISE. Contact TCG Administration (admin@trustedcomputinggroup.org) for information on specification licensing rights available through TCG membership agreements. * THIS SPECIFICATION IS PROVIDED "AS IS" WITH NO EXPRESS OR IMPLIED WARRANTIES WHATSOEVER, INCLUDING ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, COMPLETENESS, OR NONINFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS, OR ANY WARRANTY OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION OR SAMPLE. * Without limitation, TCG and its members and licensors disclaim all liability, including liability for infringement of any proprietary rights, relating to use of information in this specification and to the implementation of this specification, and TCG disclaims all liability for cost of procurement of substitute goods or services, lost profits, loss of use, loss of data or any incidental, consequential, direct, indirect, or special damages, whether under contract, tort, warranty or otherwise, arising in any way out of use or reliance upon this specification or any information herein. Any marks and brands contained herein are the property of their respective owners. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-tpm/000077500000000000000000000000001450737260700242615ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/go-tpm/LICENSE000066400000000000000000000261361450737260700252760ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/logger/000077500000000000000000000000001450737260700243355ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/logger/LICENSE000066400000000000000000000261361450737260700253520ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/s2a-go/000077500000000000000000000000001450737260700241465ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/s2a-go/LICENSE.md000066400000000000000000000261361450737260700255620ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/tink/000077500000000000000000000000001450737260700240235ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/tink/go/000077500000000000000000000000001450737260700244305ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/tink/go/LICENSE000066400000000000000000000261361450737260700254450ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/uuid/000077500000000000000000000000001450737260700240245ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/google/uuid/LICENSE000066400000000000000000000027101450737260700250310ustar00rootroot00000000000000Copyright (c) 2009,2014 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/googleapis/000077500000000000000000000000001450737260700237335ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/googleapis/enterprise-certificate-proxy/000077500000000000000000000000001450737260700315525ustar00rootroot00000000000000client/000077500000000000000000000000001450737260700327515ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/googleapis/enterprise-certificate-proxyLICENSE000066400000000000000000000261361450737260700337660ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/googleapis/enterprise-certificate-proxy/client 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/googleapis/gax-go/000077500000000000000000000000001450737260700251155ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/googleapis/gax-go/v2/000077500000000000000000000000001450737260700254445ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/googleapis/gax-go/v2/LICENSE000066400000000000000000000026771450737260700264650ustar00rootroot00000000000000Copyright 2016, Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/kardianos/000077500000000000000000000000001450737260700235555ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/kardianos/service/000077500000000000000000000000001450737260700252155ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/kardianos/service/LICENSE000066400000000000000000000015461450737260700262300ustar00rootroot00000000000000Copyright (c) 2015 Daniel Theophanes This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/pborman/000077500000000000000000000000001450737260700232405ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/pborman/uuid/000077500000000000000000000000001450737260700242065ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/pborman/uuid/LICENSE000066400000000000000000000027101450737260700252130ustar00rootroot00000000000000Copyright (c) 2009,2014 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/pkg/000077500000000000000000000000001450737260700223635ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/pkg/errors/000077500000000000000000000000001450737260700236775ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/pkg/errors/LICENSE000066400000000000000000000024401450737260700247040ustar00rootroot00000000000000Copyright (c) 2015, Dave Cheney All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/robfig/000077500000000000000000000000001450737260700230525ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/robfig/cron/000077500000000000000000000000001450737260700240135ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/robfig/cron/v3/000077500000000000000000000000001450737260700243435ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/robfig/cron/v3/LICENSE000066400000000000000000000021041450737260700253450ustar00rootroot00000000000000Copyright (C) 2012 Rob Figueiredo All Rights Reserved. MIT LICENSE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/tarm/000077500000000000000000000000001450737260700225455ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/tarm/serial/000077500000000000000000000000001450737260700240245ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/github.com/tarm/serial/LICENSE000066400000000000000000000027071450737260700250370ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/go.opencensus.io/000077500000000000000000000000001450737260700227375ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/go.opencensus.io/LICENSE000066400000000000000000000261351450737260700237530ustar00rootroot00000000000000 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.guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/000077500000000000000000000000001450737260700216005ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/000077500000000000000000000000001450737260700220475ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/crypto/000077500000000000000000000000001450737260700233675ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/crypto/LICENSE000066400000000000000000000027071450737260700244020ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/net/000077500000000000000000000000001450737260700226355ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/net/LICENSE000066400000000000000000000027071450737260700236500ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/oauth2/000077500000000000000000000000001450737260700232515ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/oauth2/LICENSE000066400000000000000000000027071450737260700242640ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/sync/000077500000000000000000000000001450737260700230235ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/sync/semaphore/000077500000000000000000000000001450737260700250065ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/sync/semaphore/LICENSE000066400000000000000000000027071450737260700260210ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/sys/000077500000000000000000000000001450737260700226655ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/sys/LICENSE000066400000000000000000000027071450737260700237000ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/text/000077500000000000000000000000001450737260700230335ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/golang.org/x/text/LICENSE000066400000000000000000000027071450737260700240460ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/000077500000000000000000000000001450737260700230535ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/api/000077500000000000000000000000001450737260700236245ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/api/LICENSE000066400000000000000000000027031450737260700246330ustar00rootroot00000000000000Copyright (c) 2011 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/api/internal/000077500000000000000000000000001450737260700254405ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/api/internal/third_party/000077500000000000000000000000001450737260700277715ustar00rootroot00000000000000uritemplates/000077500000000000000000000000001450737260700324305ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/api/internal/third_partyLICENSE000066400000000000000000000027061450737260700334420ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/api/internal/third_party/uritemplatesCopyright (c) 2013 Joshua Tacoma. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/appengine/000077500000000000000000000000001450737260700250215ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/appengine/LICENSE000066400000000000000000000261361450737260700260360ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/000077500000000000000000000000001450737260700247105ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/LICENSE000066400000000000000000000261361450737260700257250ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/googleapis/000077500000000000000000000000001450737260700270415ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/googleapis/LICENSE000066400000000000000000000261361450737260700300560ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/googleapis/api/000077500000000000000000000000001450737260700276125ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/googleapis/api/LICENSE000066400000000000000000000261361450737260700306270ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/googleapis/rpc/000077500000000000000000000000001450737260700276255ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/genproto/googleapis/rpc/LICENSE000066400000000000000000000261361450737260700306420ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/grpc/000077500000000000000000000000001450737260700240065ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/grpc/LICENSE000066400000000000000000000261361450737260700250230ustar00rootroot00000000000000 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/grpc/NOTICE.txt000066400000000000000000000010521450737260700255260ustar00rootroot00000000000000Copyright 2014 gRPC authors. 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. guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/protobuf/000077500000000000000000000000001450737260700247135ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/google.golang.org/protobuf/LICENSE000066400000000000000000000027071450737260700257260ustar00rootroot00000000000000Copyright (c) 2018 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guest-agent-20231004.02/THIRD_PARTY_LICENSES/software.sslmate.com/000077500000000000000000000000001450737260700236215ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/software.sslmate.com/src/000077500000000000000000000000001450737260700244105ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/software.sslmate.com/src/go-pkcs12/000077500000000000000000000000001450737260700261165ustar00rootroot00000000000000guest-agent-20231004.02/THIRD_PARTY_LICENSES/software.sslmate.com/src/go-pkcs12/LICENSE000066400000000000000000000030101450737260700271150ustar00rootroot00000000000000Copyright (c) 2015, 2018, 2019 Opsmate, Inc. All rights reserved. Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.guest-agent-20231004.02/gce-workload-cert-refresh.service000066400000000000000000000003011450737260700227460ustar00rootroot00000000000000[Unit] Description=GCE Workload Certificate refresh [Service] Type=oneshot ExecStart=/usr/bin/gce_workload_cert_refresh # No [Install] section - this is controlled by gce-workload-cert.timer guest-agent-20231004.02/gce-workload-cert-refresh.timer000066400000000000000000000002051450737260700224310ustar00rootroot00000000000000[Unit] Description=GCE Workload Certificate refresh timer [Timer] OnBootSec=5 OnUnitActiveSec=10m [Install] WantedBy=timers.target guest-agent-20231004.02/gce_workload_cert_refresh/000077500000000000000000000000001450737260700216205ustar00rootroot00000000000000guest-agent-20231004.02/gce_workload_cert_refresh/main.go000066400000000000000000000221211450737260700230710ustar00rootroot00000000000000// Copyright 2022 Google LLC // // 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. // gce_workload_cert_refresh downloads and rotates workload certificates for GCE VMs. package main import ( "context" "encoding/json" "fmt" "io" "os" "time" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) const ( contentDirPrefix = "/run/secrets/workload-spiffe-contents" tempSymlinkPrefix = "/run/secrets/workload-spiffe-symlink" symlink = "/run/secrets/workload-spiffe-credentials" programName = "gce_workload_certs_refresh" ) var ( // mdsClient is the client used to query Metadata server. mdsClient *metadata.Client ) func init() { mdsClient = metadata.New() } func logFormat(e logger.LogEntry) string { now := time.Now().Format("2006/01/02 15:04:05") return fmt.Sprintf("%s: %s", now, e.Message) } func getMetadata(ctx context.Context, key string) ([]byte, error) { // GCE Workload Certificate endpoints return 412 Precondition failed if the VM was // never configured with valid config values at least once. Without valid config // values GCE cannot provision the workload certificates. resp, err := mdsClient.GetKey(ctx, key, nil) if err != nil { return []byte{}, fmt.Errorf("failed to GET %q from MDS with error: %w", key, err) } return []byte(resp), nil } /* metadata key instance/gce-workload-certificates/workload-identities { "status": "OK", "workloadCredentials": { "PROJECT_ID.svc.id.goog": { "metadata": { "workload_creds_dir_path": "/var/run/secrets/workload-spiffe-credentials" }, "certificatePem": "-----BEGIN CERTIFICATE-----datahere-----END CERTIFICATE-----", "privateKeyPem": "-----BEGIN PRIVATE KEY-----datahere-----END PRIVATE KEY-----" } } } */ // WorkloadIdentities represents Workload Identities in metadata. type WorkloadIdentities struct { Status string WorkloadCredentials map[string]WorkloadCredential } // UnmarshalJSON is a custom JSON unmarshaller for WorkloadIdentities. func (wi *WorkloadIdentities) UnmarshalJSON(b []byte) error { tmp := map[string]json.RawMessage{} err := json.Unmarshal(b, &tmp) if err != nil { return err } if err := json.Unmarshal(tmp["status"], &wi.Status); err != nil { return err } wi.WorkloadCredentials = map[string]WorkloadCredential{} wcs := map[string]json.RawMessage{} if err := json.Unmarshal(tmp["workloadCredentials"], &wcs); err != nil { return err } for domain, value := range wcs { wc := WorkloadCredential{} err := json.Unmarshal(value, &wc) if err != nil { return err } wi.WorkloadCredentials[domain] = wc } return nil } // WorkloadCredential represents Workload Credentials in metadata. type WorkloadCredential struct { Metadata Metadata CertificatePem string PrivateKeyPem string } /* metadata key instance/gce-workload-certificates/root-certs { "status": "OK", "rootCertificates": { "PROJECT.svc.id.goog": { "metadata": { "workload_creds_dir_path": "/var/run/secrets/workload-spiffe-credentials" }, "rootCertificatesPem": "-----BEGIN CERTIFICATE-----datahere-----END CERTIFICATE-----" } } } */ // WorkloadTrustedRootCerts represents Workload Trusted Root Certs in metadata. type WorkloadTrustedRootCerts struct { Status string RootCertificates map[string]RootCertificate } // UnmarshalJSON is a custom JSON unmarshaller for WorkloadTrustedRootCerts func (wtrc *WorkloadTrustedRootCerts) UnmarshalJSON(b []byte) error { tmp := map[string]json.RawMessage{} err := json.Unmarshal(b, &tmp) if err != nil { return err } if err := json.Unmarshal(tmp["status"], &wtrc.Status); err != nil { return err } wtrc.RootCertificates = map[string]RootCertificate{} rcs := map[string]json.RawMessage{} if err := json.Unmarshal(tmp["rootCertificates"], &rcs); err != nil { return err } for domain, value := range rcs { rc := RootCertificate{} err := json.Unmarshal(value, &rc) if err != nil { return err } wtrc.RootCertificates[domain] = rc } return nil } // RootCertificate represents a Root Certificate in metadata type RootCertificate struct { Metadata Metadata RootCertificatesPem string } // Metadata represents Metadata in metadata type Metadata struct { WorkloadCredsDirPath string } func main() { ctx := context.Background() opts := logger.LogOpts{ LoggerName: programName, FormatFunction: logFormat, // No need for syslog. DisableLocalLogging: true, } opts.Writers = []io.Writer{os.Stderr} logger.Init(ctx, opts) defer logger.Infof("Done") // TODO: prune old dirs if err := refreshCreds(ctx); err != nil { logger.Fatalf("Error refreshCreds: %v", err.Error()) } } func refreshCreds(ctx context.Context) error { project, err := getMetadata(ctx, "project/project-id") if err != nil { return fmt.Errorf("error getting project ID: %v", err) } // Get status first so it can be written even when other endpoints are empty. certConfigStatus, err := getMetadata(ctx, "instance/gce-workload-certificates/config-status") if err != nil { // Return success when certs are not configured to avoid unnecessary systemd failed units. logger.Infof("Error getting config status, workload certificates may not be configured: %v", err) return nil } domain := fmt.Sprintf("%s.svc.id.goog", project) logger.Infof("Rotating workload credentials for trust domain %s", domain) now := time.Now().Format(time.RFC3339) contentDir := fmt.Sprintf("%s-%s", contentDirPrefix, now) tempSymlink := fmt.Sprintf("%s-%s", tempSymlinkPrefix, now) logger.Infof("Creating timestamp contents dir %s", contentDir) if err := os.MkdirAll(contentDir, 0755); err != nil { return fmt.Errorf("error creating contents dir: %v", err) } // Write config_status first even if remaining endpoints are empty. if err := os.WriteFile(fmt.Sprintf("%s/config_status", contentDir), certConfigStatus, 0644); err != nil { return fmt.Errorf("error writing config_status: %v", err) } // Handles the edge case where the config values provided for the first time may be invalid. This ensures // that the symlink directory alwasys exists and contains the config_status to surface config errors to the VM. if _, err := os.Stat(symlink); os.IsNotExist(err) { logger.Infof("Creating new symlink %s", symlink) if err := os.Symlink(contentDir, symlink); err != nil { return fmt.Errorf("error creating symlink: %v", err) } } // Now get the rest of the content. wisMd, err := getMetadata(ctx, "instance/gce-workload-certificates/workload-identities") if err != nil { return fmt.Errorf("error getting workload-identities: %v", err) } wtrcsMd, err := getMetadata(ctx, "instance/gce-workload-certificates/root-certs") if err != nil { return fmt.Errorf("error getting workload-trusted-root-certs: %v", err) } wis := WorkloadIdentities{} if err := json.Unmarshal(wisMd, &wis); err != nil { return fmt.Errorf("error unmarshaling workload identities response: %v", err) } wtrcs := WorkloadTrustedRootCerts{} if err := json.Unmarshal(wtrcsMd, &wtrcs); err != nil { return fmt.Errorf("error unmarshaling workload trusted root certs: %v", err) } if err := os.WriteFile(fmt.Sprintf("%s/certificates.pem", contentDir), []byte(wis.WorkloadCredentials[domain].CertificatePem), 0644); err != nil { return fmt.Errorf("error writing certificates.pem: %v", err) } if err := os.WriteFile(fmt.Sprintf("%s/private_key.pem", contentDir), []byte(wis.WorkloadCredentials[domain].PrivateKeyPem), 0644); err != nil { return fmt.Errorf("error writing private_key.pem: %v", err) } if err := os.WriteFile(fmt.Sprintf("%s/ca_certificates.pem", contentDir), []byte(wtrcs.RootCertificates[domain].RootCertificatesPem), 0644); err != nil { return fmt.Errorf("error writing ca_certificates.pem: %v", err) } if err := os.Symlink(contentDir, tempSymlink); err != nil { return fmt.Errorf("error creating temporary link: %v", err) } oldTarget, err := os.Readlink(symlink) if err != nil { logger.Infof("Error reading existing symlink: %v\n", err) oldTarget = "" } // Only rotate on success of all steps above. logger.Infof("Rotating symlink %s", symlink) if err := os.Rename(tempSymlink, symlink); err != nil { return fmt.Errorf("error rotating target link: %v", err) } // Clean up previous contents dir. newTarget, err := os.Readlink(symlink) if err != nil { return fmt.Errorf("error reading new symlink: %v, unable to remove old symlink target", err) } if oldTarget != newTarget { logger.Infof("Removing old content dir %s", oldTarget) if err := os.RemoveAll(oldTarget); err != nil { return fmt.Errorf("failed to remove old symlink target: %v", err) } } return nil } guest-agent-20231004.02/go.mod000066400000000000000000000041701450737260700155350ustar00rootroot00000000000000module github.com/GoogleCloudPlatform/guest-agent go 1.20 replace github.com/GoogleCloudPlatform/guest-agent/metadata => ../metadata require ( cloud.google.com/go/storage v1.31.0 github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20230710215706-450679fd88a9 github.com/go-ini/ini v1.66.6 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/golang/protobuf v1.5.3 github.com/google/go-tpm v0.9.0 github.com/google/go-tpm-tools v0.4.0 github.com/google/tink/go v1.7.0 github.com/kardianos/service v1.2.1 github.com/robfig/cron/v3 v3.0.1 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 golang.org/x/sys v0.11.0 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 software.sslmate.com/src/go-pkcs12 v0.2.1 ) require ( cloud.google.com/go v0.110.6 // indirect cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.1 // indirect cloud.google.com/go/logging v1.7.0 // indirect cloud.google.com/go/longrunning v0.5.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-sev-guest v0.7.0 // indirect github.com/google/logger v1.1.1 // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pkg/errors v0.9.1 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.134.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e // indirect ) guest-agent-20231004.02/go.sum000066400000000000000000000563651450737260700155770ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q= cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI= cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20230710215706-450679fd88a9 h1:b3geIwOPAShYtR4F0XFt+2NJXTHVTfbxUFmrpiZXHdQ= github.com/GoogleCloudPlatform/guest-logging-go v0.0.0-20230710215706-450679fd88a9/go.mod h1:6ZqSUIZRAPR5dNMWJ+FwIarFFQ9t5qalaKQs20o6h+I= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-ini/ini v1.66.6 h1:h6k2Bb0HWS/BXXHCXj4QHjxPmlIU4NK+7MuLp9SD+4k= github.com/go-ini/ini v1.66.6/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/certificate-transparency-go v1.1.2 h1:4hE0GEId6NAW28dFpC+LrRGwQX5dtmXQGDbg8+/MZOM= github.com/google/go-attestation v0.4.4-0.20230613144338-a9b6eb1eb888 h1:HURgKPRPJSozDuMHpjdV+iyFVLhB6bi1JanhGgSzI1k= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-sev-guest v0.7.0 h1:DBCABhTo7WicP27ZH/hwcCdjcmxFkxxMOQXm5hFcfp4= github.com/google/go-sev-guest v0.7.0/go.mod h1:UEi9uwoPbLdKGl1QHaq1G8pfCbQ4QP0swWX4J0k6r+Q= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-tpm-tools v0.4.0 h1:bYRZAUvQEmn11WTKCkTLRCCv4aTlOBgBBeqCK0ABT2A= github.com/google/go-tpm-tools v0.4.0/go.mod h1:G7PFUk8KKQzdYYGv/cpV9LB9sPT7czAAomnceugzNKQ= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM= github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kardianos/service v1.2.1 h1:AYndMsehS+ywIS6RB9KOlcXzteWUzxgMgBymJD7+BYk= github.com/kardianos/service v1.2.1/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw= google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg= google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108= google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e h1:S83+ibolgyZ0bqz7KEsUOPErxcv4VzlszxY+31OfB/E= google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= software.sslmate.com/src/go-pkcs12 v0.2.1 h1:tbT1jjaeFOF230tzOIRJ6U5S1jNqpsSyNjzDd58H3J8= software.sslmate.com/src/go-pkcs12 v0.2.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= guest-agent-20231004.02/google-guest-agent.conf000066400000000000000000000001641450737260700207720ustar00rootroot00000000000000description "GCE Guest Agent" start on stopped rc RUNLEVEL=[2345] oom -16 respawn exec /usr/bin/google_guest_agent guest-agent-20231004.02/google-guest-agent.service000066400000000000000000000015701450737260700215070ustar00rootroot00000000000000[Unit] Description=Google Compute Engine Guest Agent # Start before sshd in order to regenerate SSH host keys. Before=sshd.service # Start after network is online and restart when network service is restarted. # Debian/Ubuntu 16.04: networking.service # SLES/EL7: network.service (SLES via wicked.service) # EL8: NetworkManager.service # COS/Ubuntu 18.04+: systemd-networkd.service After=network-online.target syslog.service After=network.service networking.service NetworkManager.service systemd-networkd.service Wants=network-online.target PartOf=network.service networking.service NetworkManager.service systemd-networkd.service [Service] Type=notify ExecStart=/usr/bin/google_guest_agent OOMScoreAdjust=-999 Restart=always [Install] WantedBy=sshd.service WantedBy=multi-user.target WantedBy=network.service networking.service NetworkManager.service systemd-networkd.service guest-agent-20231004.02/google-shutdown-scripts.conf000066400000000000000000000002061450737260700221040ustar00rootroot00000000000000# Runs a shutdown script from metadata. start on starting rc RUNLEVEL=[06] task exec /usr/bin/google_metadata_script_runner shutdown guest-agent-20231004.02/google-shutdown-scripts.service000066400000000000000000000006351450737260700226250ustar00rootroot00000000000000[Unit] Description=Google Compute Engine Shutdown Scripts Wants=network-online.target rsyslog.service After=network-online.target rsyslog.service [Service] Type=oneshot ExecStart=/bin/true RemainAfterExit=true # This service does nothing on start, and runs shutdown scripts on stop. ExecStop=/usr/bin/google_metadata_script_runner shutdown TimeoutStopSec=0 KillMode=process [Install] WantedBy=multi-user.target guest-agent-20231004.02/google-startup-scripts.conf000066400000000000000000000002141450737260700217320ustar00rootroot00000000000000# Runs a startup script from metadata. start on started google-guest-agent and startup exec /usr/bin/google_metadata_script_runner startup guest-agent-20231004.02/google-startup-scripts.service000066400000000000000000000006111450737260700224460ustar00rootroot00000000000000[Unit] Description=Google Compute Engine Startup Scripts Wants=network-online.target rsyslog.service After=network-online.target rsyslog.service google-guest-agent.service Before=apt-daily.service [Service] Type=oneshot ExecStart=/usr/bin/google_metadata_script_runner startup #TimeoutStartSec is ignored for Type=oneshot service units. KillMode=process [Install] WantedBy=multi-user.target guest-agent-20231004.02/google_authorized_keys/000077500000000000000000000000001450737260700211725ustar00rootroot00000000000000guest-agent-20231004.02/google_authorized_keys/main.go000066400000000000000000000125401450737260700224470ustar00rootroot00000000000000// Copyright 2022 Google LLC // 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 // https://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. // GoogleAuthorizedKeys obtains SSH keys from metadata. package main import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "runtime" "strconv" "strings" "time" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) var ( programName = "GoogleAuthorizedKeysCommand" metadataURL = "http://169.254.169.254/computeMetadata/v1/" metadataRecursive = "/?recursive=true&alt=json" defaultTimeout = 2 * time.Second ) func logFormat(e logger.LogEntry) string { now := time.Now().Format("2006/01/02 15:04:05") return fmt.Sprintf("%s %s: %s", now, programName, e.Message) } func logFormatWindows(e logger.LogEntry) string { now := time.Now().Format("2006/01/02 15:04:05") // 2006/01/02 15:04:05 GCEMetadataScripts This is a log message. return fmt.Sprintf("%s %s: %s", now, programName, e.Message) } func getMetadata(key string, recurse bool) ([]byte, error) { client := &http.Client{ Timeout: defaultTimeout, } url := metadataURL + key if recurse { url += metadataRecursive } req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Add("Metadata-Flavor", "Google") var res *http.Response // Retry forever, increase sleep between retries (up to 5 times) in order // to wait for slow network initialization. var rt time.Duration for i := 1; ; i++ { res, err = client.Do(req) if err == nil { break } if i < 6 { rt = time.Duration(3*i) * time.Second } logger.Errorf("error connecting to metadata server, retrying in %s, error: %v", rt, err) time.Sleep(rt) } defer res.Body.Close() md, err := io.ReadAll(res.Body) if err != nil { return nil, err } return md, nil } func parseSSHKeys(username string, keys []string) []string { var keyList []string for _, key := range keys { keySplit := strings.SplitN(key, ":", 2) if len(keySplit) != 2 { continue } user, keyVal, err := utils.GetUserKey(key) if err == nil { err = utils.ValidateUserKey(user, keyVal) } if err != nil { continue } if user == username { keyList = append(keyList, keyVal) } } return keyList } func getUserKeys(username string, instanceAttributes *attributes, projectAttributes *attributes) []string { var userKeyList []string instanceKeyList := parseSSHKeys(username, instanceAttributes.SSHKeys) userKeyList = append(userKeyList, instanceKeyList...) if !instanceAttributes.BlockProjectSSHKeys { projectKeyList := parseSSHKeys(username, projectAttributes.SSHKeys) userKeyList = append(userKeyList, projectKeyList...) } return userKeyList } func checkWinSSHEnabled(instanceAttributes *attributes, projectAttributes *attributes) bool { if instanceAttributes.EnableWindowsSSH != nil { return bool(*instanceAttributes.EnableWindowsSSH) } else if projectAttributes.EnableWindowsSSH != nil { return bool(*projectAttributes.EnableWindowsSSH) } return false } type attributes struct { EnableWindowsSSH *bool BlockProjectSSHKeys bool SSHKeys []string } func getMetadataAttributes(metadataKey string) (*attributes, error) { var a attributes type jsonAttributes struct { EnableWindowsSSH string `json:"enable-windows-ssh"` BlockProjectSSHKeys string `json:"block-project-ssh-keys"` SSHKeys string `json:"ssh-keys"` } var ja jsonAttributes metadata, err := getMetadata(metadataKey, true) if err != nil { return nil, err } if err := json.Unmarshal(metadata, &ja); err != nil { return nil, err } value, err := strconv.ParseBool(ja.BlockProjectSSHKeys) if err == nil { a.BlockProjectSSHKeys = value } value, err = strconv.ParseBool(ja.EnableWindowsSSH) if err == nil { a.EnableWindowsSSH = &value } if ja.SSHKeys != "" { a.SSHKeys = strings.Split(ja.SSHKeys, "\n") } return &a, nil } func main() { ctx := context.Background() username := os.Args[1] opts := logger.LogOpts{ LoggerName: programName, FormatFunction: logFormat, } if runtime.GOOS == "windows" { opts.Writers = []io.Writer{&utils.SerialPort{Port: "COM1"}, os.Stderr} opts.FormatFunction = logFormatWindows } else { opts.Writers = []io.Writer{os.Stderr} } logger.Init(ctx, opts) instanceAttributes, err := getMetadataAttributes("instance/attributes/") if err != nil { logger.Errorf("Cannot read instance metadata attributes: %v", err) os.Exit(1) } projectAttributes, err := getMetadataAttributes("project/attributes/") if err != nil { logger.Errorf("Cannot read project metadata attributes: %v", err) os.Exit(1) } if runtime.GOOS == "windows" && !checkWinSSHEnabled(instanceAttributes, projectAttributes) { logger.Errorf("Windows SSH not enabled with 'enable-windows-ssh' metadata key.") os.Exit(1) } userKeyList := getUserKeys(username, instanceAttributes, projectAttributes) fmt.Print(strings.Join(userKeyList, "\n")) } guest-agent-20231004.02/google_authorized_keys/main_test.go000066400000000000000000000155451450737260700235160ustar00rootroot00000000000000// Copyright 2022 Google LLC // 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 // https://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. package main import ( "fmt" "net/http" "net/http/httptest" "reflect" "strconv" "strings" "testing" "time" ) func stringSliceEqual(a, b []string) bool { if len(a) != len(b) { return false } for i, v := range a { if v != b[i] { return false } } return true } func boolToStr(b *bool) string { if b == nil { return "" } return strconv.FormatBool(*b) } var t = true var f = false var truebool *bool = &t var falsebool *bool = &f func TestParseSSHKeys(t *testing.T) { keys := []string{ "# Here is some random data in the file.", "usera:ssh-rsa AAAA1234USERA", "userb:ssh-rsa AAAA1234USERB", `usera:ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, `usera:ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2020-04-23T12:34:56+0000"}`, } expected := []string{ "ssh-rsa AAAA1234USERA", `ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, } user := "usera" if got, want := parseSSHKeys(user, keys), expected; !stringSliceEqual(got, want) { t.Errorf("ParseSSHKeys(%s,%s) incorrect return: got %v, want %v", user, keys, got, want) } } func TestCheckWinSSHEnabled(t *testing.T) { tests := []struct { instanceEnable *bool projectEnable *bool expected bool }{ { instanceEnable: truebool, projectEnable: nil, expected: true, }, { instanceEnable: falsebool, projectEnable: nil, expected: false, }, { instanceEnable: falsebool, projectEnable: truebool, expected: false, }, { instanceEnable: nil, projectEnable: truebool, expected: true, }, { instanceEnable: nil, projectEnable: falsebool, expected: false, }, { instanceEnable: nil, projectEnable: nil, expected: false, }, } for _, tt := range tests { instanceAttributes := attributes{EnableWindowsSSH: tt.instanceEnable} projectAttributes := attributes{EnableWindowsSSH: tt.projectEnable} if got, want := checkWinSSHEnabled(&instanceAttributes, &projectAttributes), tt.expected; got != want { t.Errorf("checkWinSSHEnabled(%s, %s) incorrect return: got %v, want %v", boolToStr(tt.instanceEnable), boolToStr(tt.projectEnable), got, want) } } } func TestGetUserKeysNew(t *testing.T) { tests := []struct { userName string instanceMetadata attributes projectMetadata attributes expectedKeys []string }{ { userName: "name", instanceMetadata: attributes{BlockProjectSSHKeys: false, SSHKeys: []string{"name:ssh-rsa [KEY] instance1", "othername:ssh-rsa [KEY] instance2"}, }, projectMetadata: attributes{ SSHKeys: []string{"name:ssh-rsa [KEY] project1", "othername:ssh-rsa [KEY] project2"}, }, expectedKeys: []string{"ssh-rsa [KEY] instance1", "ssh-rsa [KEY] project1"}, }, { userName: "name", instanceMetadata: attributes{BlockProjectSSHKeys: true, SSHKeys: []string{"name:ssh-rsa [KEY] instance1", "othername:ssh-rsa [KEY] instance2"}, }, projectMetadata: attributes{ SSHKeys: []string{"name:ssh-rsa [KEY] project1", "othername:ssh-rsa [KEY] project2"}, }, expectedKeys: []string{"ssh-rsa [KEY] instance1"}, }, { userName: "name", instanceMetadata: attributes{BlockProjectSSHKeys: false, SSHKeys: []string{"name:ssh-rsa [KEY] instance1", "othername:ssh-rsa [KEY] instance2"}, }, projectMetadata: attributes{ SSHKeys: nil, }, expectedKeys: []string{"ssh-rsa [KEY] instance1"}, }, { userName: "name", instanceMetadata: attributes{BlockProjectSSHKeys: false, SSHKeys: nil, }, projectMetadata: attributes{ SSHKeys: []string{"name:ssh-rsa [KEY] project1", "othername:ssh-rsa [KEY] project2"}, }, expectedKeys: []string{"ssh-rsa [KEY] project1"}, }, } for count, tt := range tests { if got, want := getUserKeys(tt.userName, &tt.instanceMetadata, &tt.projectMetadata), tt.expectedKeys; !stringSliceEqual(got, want) { t.Errorf("getUserKeys[%d] incorrect return: got %v, want %v", count, got, want) } } } func TestGetMetadataAttributes(t *testing.T) { tests := []struct { metadata string att *attributes expectErr bool }{ { metadata: `{"enable-windows-ssh":"true","ssh-keys":"name:ssh-rsa [KEY] instance1\nothername:ssh-rsa [KEY] instance2","block-project-ssh-keys":"false","other-metadata":"foo"}`, att: &attributes{EnableWindowsSSH: truebool, SSHKeys: []string{"name:ssh-rsa [KEY] instance1", "othername:ssh-rsa [KEY] instance2"}, BlockProjectSSHKeys: false}, expectErr: false, }, { metadata: `{"enable-windows-ssh":"true","ssh-keys":"name:ssh-rsa [KEY] instance1\nothername:ssh-rsa [KEY] instance2","block-project-ssh-keys":"true","other-metadata":"foo"}`, att: &attributes{EnableWindowsSSH: truebool, SSHKeys: []string{"name:ssh-rsa [KEY] instance1", "othername:ssh-rsa [KEY] instance2"}, BlockProjectSSHKeys: true}, expectErr: false, }, { metadata: `{"ssh-keys":"name:ssh-rsa [KEY] instance1\nothername:ssh-rsa [KEY] instance2","block-project-ssh-keys":"false","other-metadata":"foo"}`, att: &attributes{EnableWindowsSSH: nil, SSHKeys: []string{"name:ssh-rsa [KEY] instance1", "othername:ssh-rsa [KEY] instance2"}, BlockProjectSSHKeys: false}, expectErr: false, }, { metadata: `{"enable-windows-ssh":"false","ssh-keys":"name:ssh-rsa [KEY] instance1\nothername:ssh-rsa [KEY] instance2","other-metadata":"foo"}`, att: &attributes{EnableWindowsSSH: falsebool, SSHKeys: []string{"name:ssh-rsa [KEY] instance1", "othername:ssh-rsa [KEY] instance2"}, BlockProjectSSHKeys: false}, expectErr: false, }, { metadata: `BADJSON`, att: nil, expectErr: true, }, } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get test number from request path tnum, _ := strconv.Atoi(strings.Split(r.URL.Path, "/")[2]) fmt.Fprintf(w, tests[tnum].metadata) })) defer ts.Close() metadataURL = ts.URL defaultTimeout = 1 * time.Second for count, tt := range tests { want := tt.att hasErr := false reqStr := fmt.Sprintf("/attributes/%d", count) got, err := getMetadataAttributes(reqStr) if err != nil { hasErr = true } if !reflect.DeepEqual(got, want) || hasErr != tt.expectErr { t.Errorf("Failed: Got: %v, Want: %v, Error: %v", got, want, err) } } } guest-agent-20231004.02/google_guest_agent/000077500000000000000000000000001450737260700202665ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/accounts_unix.go000066400000000000000000000031761450737260700235060ustar00rootroot00000000000000// Copyright 2019 Google LLC // 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 // https://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. //go:build !windows package main import ( "context" "fmt" "os" "os/user" "syscall" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" ) func getUID(path string) string { if dir, err := os.Stat(path); err == nil { if stat, ok := dir.Sys().(*syscall.Stat_t); ok { return fmt.Sprintf("%d", stat.Uid) } } return "" } func createUser(ctx context.Context, username, uid string) error { config := cfg.Get() useradd := config.Accounts.UserAddCmd if uid != "" { useradd = fmt.Sprintf("%s -u %s", useradd, uid) } cmd, args := createUserGroupCmd(useradd, username, "") return run.Quiet(ctx, cmd, args...) } func addUserToGroup(ctx context.Context, user, group string) error { config := cfg.Get() gpasswdadd := config.Accounts.GPasswdAddCmd cmd, args := createUserGroupCmd(gpasswdadd, user, group) return run.Quiet(ctx, cmd, args...) } func userExists(name string) (bool, error) { if _, err := user.Lookup(name); err != nil { return false, err } return true, nil } guest-agent-20231004.02/google_guest_agent/accounts_windows.go000066400000000000000000000121571450737260700242140ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "context" "fmt" "syscall" "unsafe" "golang.org/x/sys/windows" ) var ( netAPI32 = windows.NewLazySystemDLL("netapi32.dll") procNetUserAdd = netAPI32.NewProc("NetUserAdd") procNetUserGetInfo = netAPI32.NewProc("NetUserGetInfo") procNetUserSetInfo = netAPI32.NewProc("NetUserSetInfo") procNetLocalGroupAddMembers = netAPI32.NewProc("NetLocalGroupAddMembers") ) type ( USER_INFO_0 struct { Usri0_name LPWSTR } USER_INFO_1 struct { Usri1_name LPWSTR Usri1_password LPWSTR Usri1_password_age DWORD Usri1_priv DWORD Usri1_home_dir LPWSTR Usri1_comment LPWSTR Usri1_flags DWORD Usri1_script_path LPWSTR } LOCALGROUP_MEMBERS_INFO_0 struct { Lgrmi0_sid *syscall.SID } USER_INFO_1003 struct { Usri1003_password LPWSTR } ) const ( USER_PRIV_GUEST = 0 USER_PRIV_USER = 1 USER_PRIV_ADMIN = 2 UF_SCRIPT = 0x0001 UF_ACCOUNTDISABLE = 0x0002 UF_HOMEDIR_REQUIRED = 0x0008 UF_LOCKOUT = 0x0010 UF_PASSWD_NOTREQD = 0x0020 UF_PASSWD_CANT_CHANGE = 0x0040 UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x0080 UF_TEMP_DUPLICATE_ACCOUNT = 0x0100 UF_NORMAL_ACCOUNT = 0x0200 UF_INTERDOMAIN_TRUST_ACCOUNT = 0x0800 UF_WORKSTATION_TRUST_ACCOUNT = 0x1000 UF_SERVER_TRUST_ACCOUNT = 0x2000 UF_DONT_EXPIRE_PASSWD = 0x10000 UF_MNS_LOGON_ACCOUNT = 0x20000 UF_SMARTCARD_REQUIRED = 0x40000 UF_TRUSTED_FOR_DELEGATION = 0x80000 UF_NOT_DELEGATED = 0x100000 UF_USE_DES_KEY_ONLY = 0x200000 UF_DONT_REQUIRE_PREAUTH = 0x400000 UF_PASSWORD_EXPIRED = 0x800000 UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000 UF_NO_AUTH_DATA_REQUIRED = 0x2000000 UF_PARTIAL_SECRETS_ACCOUNT = 0x4000000 UF_USE_AES_KEYS = 0x8000000 ) func resetPwd(username, pwd string) error { uPtr, err := syscall.UTF16PtrFromString(username) if err != nil { return fmt.Errorf("error encoding username to UTF16: %v", err) } pPtr, err := syscall.UTF16PtrFromString(pwd) if err != nil { return fmt.Errorf("error encoding password to UTF16: %v", err) } ret, _, _ := procNetUserSetInfo.Call( uintptr(0), uintptr(unsafe.Pointer(uPtr)), uintptr(1003), uintptr(unsafe.Pointer(&USER_INFO_1003{pPtr})), uintptr(0)) if ret != 0 { return fmt.Errorf("nonzero return code from NetUserSetInfo: %s", syscall.Errno(ret)) } return nil } func addUserToGroup(ctx context.Context, username, group string) error { gPtr, err := syscall.UTF16PtrFromString(group) if err != nil { return fmt.Errorf("error encoding group to UTF16: %v", err) } sid, _, _, err := syscall.LookupSID("", username) if err != nil { return err } sArray := []LOCALGROUP_MEMBERS_INFO_0{{sid}} ret, _, _ := procNetLocalGroupAddMembers.Call( uintptr(0), uintptr(unsafe.Pointer(gPtr)), uintptr(0), uintptr(unsafe.Pointer(&sArray[0])), uintptr(1), ) // Ignore ERROR_MEMBER_IN_ALIAS (1378). if ret != 0 && ret != 1378 { return fmt.Errorf("nonzero return code from NetLocalGroupAddMembers: %s", syscall.Errno(ret)) } return nil } func createUser(ctx context.Context, username, pwd string) error { uPtr, err := syscall.UTF16PtrFromString(username) if err != nil { return fmt.Errorf("error encoding username to UTF16: %v", err) } pPtr, err := syscall.UTF16PtrFromString(pwd) if err != nil { return fmt.Errorf("error encoding password to UTF16: %v", err) } uInfo1 := USER_INFO_1{ Usri1_name: uPtr, Usri1_password: pPtr, Usri1_priv: USER_PRIV_USER, Usri1_flags: UF_SCRIPT | UF_NORMAL_ACCOUNT | UF_DONT_EXPIRE_PASSWD, } ret, _, _ := procNetUserAdd.Call( uintptr(0), uintptr(1), uintptr(unsafe.Pointer(&uInfo1)), uintptr(0), ) if ret != 0 { return fmt.Errorf("nonzero return code from NetUserAdd: %s", syscall.Errno(ret)) } return nil } func userExists(name string) (bool, error) { uPtr, err := syscall.UTF16PtrFromString(name) if err != nil { return false, fmt.Errorf("error encoding username to UTF16: %v", err) } ret, _, _ := procNetUserGetInfo.Call( uintptr(0), uintptr(unsafe.Pointer(uPtr)), uintptr(1), uintptr(unsafe.Pointer(&USER_INFO_0{})), ) if ret != 0 { return false, fmt.Errorf("nonzero return code from NetUserGetInfo: %s", syscall.Errno(ret)) } return true, nil } func getUID(path string) string { return "" } guest-agent-20231004.02/google_guest_agent/addresses.go000066400000000000000000000457121450737260700226030ustar00rootroot00000000000000// Copyright 2022 Google LLC // // 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. package main import ( "context" "errors" "fmt" "net" "os" "reflect" "runtime" "strings" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) var ( addressKey = regKeyBase + `\ForwardedIps` oldWSFCAddresses string oldWSFCEnable bool interfacesEnabled bool interfaces []net.Interface ) type addressMgr struct{} func (a *addressMgr) parseWSFCAddresses(config *cfg.Sections) string { if config.WSFC != nil && config.WSFC.Addresses != "" { return config.WSFC.Addresses } if newMetadata.Instance.Attributes.WSFCAddresses != "" { return newMetadata.Instance.Attributes.WSFCAddresses } if newMetadata.Project.Attributes.WSFCAddresses != "" { return newMetadata.Project.Attributes.WSFCAddresses } return "" } func (a *addressMgr) parseWSFCEnable(config *cfg.Sections) bool { if config.WSFC != nil { return config.WSFC.Enable } if newMetadata.Instance.Attributes.EnableWSFC != nil { return *newMetadata.Instance.Attributes.EnableWSFC } if newMetadata.Project.Attributes.EnableWSFC != nil { return *newMetadata.Project.Attributes.EnableWSFC } return false } func getForwardsFromRegistry(mac string) ([]string, error) { regFwdIPs, err := readRegMultiString(addressKey, mac) if err == errRegNotExist { // The old agent stored MAC addresses without the ':', // check for those and clean them up. oldName := strings.Replace(mac, ":", "", -1) regFwdIPs, err = readRegMultiString(addressKey, oldName) if err == nil { deleteRegKey(addressKey, oldName) } } else if err != nil { return nil, err } return regFwdIPs, nil } func compareRoutes(configuredRoutes, desiredRoutes []string) (toAdd, toRm []string) { for _, desiredRoute := range desiredRoutes { if !utils.ContainsString(desiredRoute, configuredRoutes) { toAdd = append(toAdd, desiredRoute) } } for _, configuredRoute := range configuredRoutes { if !utils.ContainsString(configuredRoute, desiredRoutes) { toRm = append(toRm, configuredRoute) } } return toAdd, toRm } var badMAC []string func getInterfaceByMAC(mac string) (net.Interface, error) { hwaddr, err := net.ParseMAC(mac) if err != nil { return net.Interface{}, err } for _, iface := range interfaces { if iface.HardwareAddr.String() == hwaddr.String() { return iface, nil } } return net.Interface{}, fmt.Errorf("no interface found with MAC %s", mac) } // https://www.ietf.org/rfc/rfc1354.txt // Only fields that we currently care about. type ipForwardEntry struct { ipForwardDest net.IP ipForwardMask net.IPMask ipForwardNextHop net.IP ipForwardIfIndex int32 ipForwardMetric1 int32 } // TODO: getLocalRoutes and getIPForwardEntries should be merged. func getLocalRoutes(ctx context.Context, config *cfg.Sections, ifname string) ([]string, error) { if runtime.GOOS == "windows" { return nil, errors.New("getLocalRoutes unimplemented on Windows") } protoID := config.IPForwarding.EthernetProtoID args := fmt.Sprintf("route list table local type local scope host dev %s proto %s", ifname, protoID) out := run.WithOutput(ctx, "ip", strings.Split(args, " ")...) if out.ExitCode != 0 { return nil, error(out) } var res []string for _, line := range strings.Split(out.StdOut, "\n") { line = strings.TrimPrefix(line, "local ") line = strings.TrimSpace(line) if line != "" { res = append(res, line) } } // and again for IPv6 routes, without 'scope host' which is IPv4 only args = fmt.Sprintf("-6 route list table local type local dev %s proto %s", ifname, protoID) out = run.WithOutput(ctx, "ip", strings.Split(args, " ")...) if out.ExitCode != 0 { return nil, error(out) } for _, line := range strings.Split(out.StdOut, "\n") { line = strings.TrimPrefix(line, "local ") line = strings.Split(line, " ")[0] line = strings.TrimSpace(line) if line != "" { res = append(res, line) } } return res, nil } // TODO: addLocalRoute and addRoute should be merged with the addition of ipForwardType to ipForwardEntry. func addLocalRoute(ctx context.Context, config *cfg.Sections, ip, ifname string) error { if runtime.GOOS == "windows" { return errors.New("addLocalRoute unimplemented on Windows") } // TODO: Subnet size should be parsed from alias IP entries. if !strings.Contains(ip, "/") { ip = ip + "/32" } protoID := config.IPForwarding.EthernetProtoID args := fmt.Sprintf("route add to local %s scope host dev %s proto %s", ip, ifname, protoID) return run.Quiet(ctx, "ip", strings.Split(args, " ")...) } // TODO: removeLocalRoute should be changed to removeIPForwardEntry and match getIPForwardEntries. func removeLocalRoute(ctx context.Context, config *cfg.Sections, ip, ifname string) error { if runtime.GOOS == "windows" { return errors.New("removeLocalRoute unimplemented on Windows") } // TODO: Subnet size should be parsed from alias IP entries. if !strings.Contains(ip, "/") { ip = ip + "/32" } protoID := config.IPForwarding.EthernetProtoID args := fmt.Sprintf("route delete to local %s scope host dev %s proto %s", ip, ifname, protoID) return run.Quiet(ctx, "ip", strings.Split(args, " ")...) } // Filter out forwarded ips based on WSFC (Windows Failover Cluster Settings). // If only EnableWSFC is set, all ips in the ForwardedIps and TargetInstanceIps will be ignored. // If WSFCAddresses is set (with or without EnableWSFC), only ips in the list will be filtered out. // TODO return a filtered list rather than modifying the metadata object. liamh@15-11-19 func (a *addressMgr) applyWSFCFilter(config *cfg.Sections) { wsfcAddresses := a.parseWSFCAddresses(config) var wsfcAddrs []string for _, wsfcAddr := range strings.Split(wsfcAddresses, ",") { if wsfcAddr == "" { continue } if net.ParseIP(wsfcAddr) == nil { logger.Errorf("Address for WSFC is not in valid form %s", wsfcAddr) continue } wsfcAddrs = append(wsfcAddrs, wsfcAddr) } if len(wsfcAddrs) != 0 { interfaces := newMetadata.Instance.NetworkInterfaces for idx := range interfaces { var filteredForwardedIps []string for _, ip := range interfaces[idx].ForwardedIps { if !utils.ContainsString(ip, wsfcAddrs) { filteredForwardedIps = append(filteredForwardedIps, ip) } } interfaces[idx].ForwardedIps = filteredForwardedIps var filteredTargetInstanceIps []string for _, ip := range interfaces[idx].TargetInstanceIps { if !utils.ContainsString(ip, wsfcAddrs) { filteredTargetInstanceIps = append(filteredTargetInstanceIps, ip) } } interfaces[idx].TargetInstanceIps = filteredTargetInstanceIps } } else { wsfcEnable := a.parseWSFCEnable(config) if wsfcEnable { for idx := range newMetadata.Instance.NetworkInterfaces { newMetadata.Instance.NetworkInterfaces[idx].ForwardedIps = nil newMetadata.Instance.NetworkInterfaces[idx].TargetInstanceIps = nil } } } } func (a *addressMgr) Diff(ctx context.Context) (bool, error) { config := cfg.Get() wsfcAddresses := a.parseWSFCAddresses(config) wsfcEnable := a.parseWSFCEnable(config) diff := !reflect.DeepEqual(newMetadata.Instance.NetworkInterfaces, oldMetadata.Instance.NetworkInterfaces) || wsfcEnable != oldWSFCEnable || wsfcAddresses != oldWSFCAddresses oldWSFCAddresses = wsfcAddresses oldWSFCEnable = wsfcEnable return diff, nil } func (a *addressMgr) Timeout(ctx context.Context) (bool, error) { return false, nil } func (a *addressMgr) Disabled(ctx context.Context) (bool, error) { config := cfg.Get() // Local configuration takes precedence over metadata's configuration. if config.AddressManager != nil { return config.AddressManager.Disable, nil } if newMetadata.Instance.Attributes.DisableAddressManager != nil { return *newMetadata.Instance.Attributes.DisableAddressManager, nil } if newMetadata.Project.Attributes.DisableAddressManager != nil { return *newMetadata.Project.Attributes.DisableAddressManager, nil } // This is the linux config key, defaulting to true. On Linux, the // config file has lower priority since we ship a file with defaults. return !config.Daemons.NetworkDaemon, nil } func (a *addressMgr) Set(ctx context.Context) error { config := cfg.Get() if runtime.GOOS == "windows" { a.applyWSFCFilter(config) } var err error interfaces, err = net.Interfaces() if err != nil { return fmt.Errorf("error populating interfaces: %v", err) } if config.NetworkInterfaces.Setup { if runtime.GOOS != "windows" { logger.Debugf("Configure IPv6") if err := configureIPv6(ctx); err != nil { // Continue through IPv6 configuration errors. logger.Errorf("Error configuring IPv6: %v", err) } } if runtime.GOOS != "windows" && !interfacesEnabled { logger.Debugf("Enable network interfaces") if err := enableNetworkInterfaces(ctx, config); err != nil { return err } interfacesEnabled = true } } if !config.NetworkInterfaces.IPForwarding { return nil } logger.Debugf("Add routes for aliases, forwarded IP and target-instance IPs") // Add routes for IP aliases, forwarded and target-instance IPs. for _, ni := range newMetadata.Instance.NetworkInterfaces { iface, err := getInterfaceByMAC(ni.Mac) if err != nil { if !utils.ContainsString(ni.Mac, badMAC) { logger.Errorf("Error getting interface: %s", err) badMAC = append(badMAC, ni.Mac) } continue } wantIPs := ni.ForwardedIps wantIPs = append(wantIPs, ni.ForwardedIpv6s...) if config.IPForwarding.TargetInstanceIPs { wantIPs = append(wantIPs, ni.TargetInstanceIps...) } // IP Aliases are not supported on windows. if runtime.GOOS != "windows" && config.IPForwarding.IPAliases { wantIPs = append(wantIPs, ni.IPAliases...) } var forwardedIPs []string var configuredIPs []string if runtime.GOOS == "windows" { addrs, err := iface.Addrs() if err != nil { logger.Errorf("Error getting addresses for interface %s: %s", iface.Name, err) } for _, addr := range addrs { configuredIPs = append(configuredIPs, strings.TrimSuffix(addr.String(), "/32")) } regFwdIPs, err := getForwardsFromRegistry(ni.Mac) if err != nil { logger.Errorf("Error getting forwards from registry: %s", err) continue } for _, ip := range configuredIPs { // Only add to `forwardedIPs` if it is recorded in the registry. if utils.ContainsString(ip, regFwdIPs) { forwardedIPs = append(forwardedIPs, ip) } } } else { forwardedIPs, err = getLocalRoutes(ctx, config, iface.Name) if err != nil { logger.Errorf("Error getting routes: %v", err) continue } } // Trims any '/32' suffix for consistency. trimSuffix := func(entries []string) []string { var res []string for _, entry := range entries { res = append(res, strings.TrimSuffix(entry, "/32")) } return res } forwardedIPs = trimSuffix(forwardedIPs) wantIPs = trimSuffix(wantIPs) toAdd, toRm := compareRoutes(forwardedIPs, wantIPs) if len(toAdd) != 0 || len(toRm) != 0 { var msg string msg = fmt.Sprintf("Changing forwarded IPs for %s from %q to %q by", ni.Mac, forwardedIPs, wantIPs) if len(toAdd) != 0 { msg += fmt.Sprintf(" adding %q", toAdd) } if len(toRm) != 0 { if len(toAdd) != 0 { msg += " and" } msg += fmt.Sprintf(" removing %q", toRm) } logger.Infof(msg) } var registryEntries []string for _, ip := range wantIPs { // If the IP is not in toAdd, add to registry list and continue. if !utils.ContainsString(ip, toAdd) { registryEntries = append(registryEntries, ip) continue } var err error if runtime.GOOS == "windows" { // Don't addAddress if this is already configured. if !utils.ContainsString(ip, configuredIPs) { err = addAddress(net.ParseIP(ip), net.IPv4Mask(255, 255, 255, 255), uint32(iface.Index)) } } else { err = addLocalRoute(ctx, config, ip, iface.Name) } if err == nil { registryEntries = append(registryEntries, ip) } else { logger.Errorf("error adding route: %v", err) } } for _, ip := range toRm { var err error if runtime.GOOS == "windows" { if !utils.ContainsString(ip, configuredIPs) { continue } err = removeAddress(net.ParseIP(ip), uint32(iface.Index)) } else { err = removeLocalRoute(ctx, config, ip, iface.Name) } if err != nil { logger.Errorf("error removing route: %v", err) // Add IPs we fail to remove to registry to maintain accurate record. registryEntries = append(registryEntries, ip) } } if runtime.GOOS == "windows" { if err := writeRegMultiString(addressKey, ni.Mac, registryEntries); err != nil { logger.Errorf("error writing registry: %s", err) } } } return nil } // Enables or disables IPv6 on network interfaces. func configureIPv6(ctx context.Context) error { var newNi, oldNi metadata.NetworkInterfaces if len(newMetadata.Instance.NetworkInterfaces) == 0 { return fmt.Errorf("no interfaces found in metadata") } newNi = newMetadata.Instance.NetworkInterfaces[0] if len(oldMetadata.Instance.NetworkInterfaces) > 0 { oldNi = oldMetadata.Instance.NetworkInterfaces[0] } iface, err := getInterfaceByMAC(newNi.Mac) if err != nil { return err } switch { case oldNi.DHCPv6Refresh != "" && newNi.DHCPv6Refresh == "", newNi.DHCPv6Refresh == "" && len(oldMetadata.Instance.NetworkInterfaces) == 0: // disable // uses empty old interface slice to indicate this is first-run. // Before obtaining or releasing an IPv6 lease, we wait for // 'tentative' IPs as part of SLAAC. We wait up to 5 seconds // for this condition to automatically resolve. tentative := []string{"-6", "-o", "a", "s", "dev", iface.Name, "scope", "link", "tentative"} for i := 0; i < 5; i++ { res := run.WithOutput(ctx, "ip", tentative...) if res.ExitCode == 0 && res.StdOut == "" { break } time.Sleep(1 * time.Second) } if err := run.Quiet(ctx, "dhclient", "-r", "-6", "-1", "-v", iface.Name); err != nil { return err } case oldNi.DHCPv6Refresh == "" && newNi.DHCPv6Refresh != "": // enable tentative := []string{"-6", "-o", "a", "s", "dev", iface.Name, "scope", "link", "tentative"} for i := 0; i < 5; i++ { res := run.WithOutput(ctx, "ip", tentative...) if res.ExitCode == 0 && res.StdOut == "" { break } time.Sleep(1 * time.Second) } val := fmt.Sprintf("net.ipv6.conf.%s.accept_ra_rt_info_max_plen=128", iface.Name) if err := run.Quiet(ctx, "sysctl", val); err != nil { return err } if err := run.Quiet(ctx, "dhclient", "-1", "-6", "-v", iface.Name); err != nil { return err } } return nil } // enableNetworkInterfaces runs `dhclient eth1 eth2 ... ethN` // and `dhclient -6 eth1 eth2 ... ethN`. // On RHEL7, it also calls disableNM for each interface. // On SLES, it calls enableSLESInterfaces instead of dhclient. func enableNetworkInterfaces(ctx context.Context, config *cfg.Sections) error { if len(newMetadata.Instance.NetworkInterfaces) < 2 { return nil } var googleInterfaces []string // The primary (first) interface is managed by the OS, we only handle // secondary interfaces in this code. for _, ni := range newMetadata.Instance.NetworkInterfaces[1:] { iface, err := getInterfaceByMAC(ni.Mac) if err != nil { if !utils.ContainsString(ni.Mac, badMAC) { logger.Errorf("Error getting interface: %s", err) badMAC = append(badMAC, ni.Mac) } continue } googleInterfaces = append(googleInterfaces, iface.Name) } var googleIpv6Interfaces []string for _, ni := range newMetadata.Instance.NetworkInterfaces[1:] { if ni.DHCPv6Refresh == "" { // This interface is not IPv6 enabled continue } iface, err := getInterfaceByMAC(ni.Mac) if err != nil { if !utils.ContainsString(ni.Mac, badMAC) { logger.Errorf("Error getting interface: %s", err) badMAC = append(badMAC, ni.Mac) } continue } googleIpv6Interfaces = append(googleIpv6Interfaces, iface.Name) } switch { case osInfo.OS == "sles": return enableSLESInterfaces(ctx, googleInterfaces) case (osInfo.OS == "rhel" || osInfo.OS == "centos") && osInfo.Version.Major >= 7: for _, iface := range googleInterfaces { err := disableNM(iface) if err != nil { return err } } fallthrough default: dhcpCommand := config.NetworkInterfaces.DHCPCommand if dhcpCommand != "" { tokens := strings.Split(dhcpCommand, " ") return run.Quiet(ctx, tokens[0], tokens[1:]...) } // Try IPv4 first as it's higher priority. if err := run.Quiet(ctx, "dhclient", googleInterfaces...); err != nil { return err } if len(googleIpv6Interfaces) == 0 { return nil } for _, iface := range googleIpv6Interfaces { // Enable kernel to accept to route advertisements. val := fmt.Sprintf("net.ipv6.conf.%s.accept_ra_rt_info_max_plen=128", iface) if err := run.Quiet(ctx, "sysctl", val); err != nil { return err } } var dhclientArgs6 []string dhclientArgs6 = append([]string{"-6"}, googleIpv6Interfaces...) return run.Quiet(ctx, "dhclient", dhclientArgs6...) } } // enableSLESInterfaces writes one ifcfg file for each interface, then // runs `wicked ifup eth1 eth2 ... ethN` func enableSLESInterfaces(ctx context.Context, interfaces []string) error { var err error var priority = 10100 for _, iface := range interfaces { logger.Debugf("write enabling ifcfg-%s config", iface) var ifcfg *os.File ifcfg, err = os.Create("/etc/sysconfig/network/ifcfg-" + iface) if err != nil { return err } defer closer(ifcfg) contents := []string{ googleComment, "STARTMODE=hotplug", // NOTE: 'dhcp' is the dhcp4+dhcp6 option. "BOOTPROTO=dhcp", fmt.Sprintf("DHCLIENT_ROUTE_PRIORITY=%d", priority), } _, err = ifcfg.WriteString(strings.Join(contents, "\n")) if err != nil { return err } priority += 100 } args := append([]string{"ifup", "--timeout", "1"}, interfaces...) return run.Quiet(ctx, "/usr/sbin/wicked", args...) } // disableNM writes an ifcfg file with DHCP and NetworkManager disabled. func disableNM(iface string) error { logger.Debugf("write disabling ifcfg-%s config", iface) filename := "/etc/sysconfig/network-scripts/ifcfg-" + iface ifcfg, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err == nil { defer closer(ifcfg) contents := []string{ googleComment, fmt.Sprintf("DEVICE=%s", iface), "BOOTPROTO=none", "DEFROUTE=no", "IPV6INIT=no", "NM_CONTROLLED=no", "NOZEROCONF=yes", } _, err = ifcfg.WriteString(strings.Join(contents, "\n")) return err } if os.IsExist(err) { return nil } return err } guest-agent-20231004.02/google_guest_agent/addresses_integ_test.go000066400000000000000000000054111450737260700250200ustar00rootroot00000000000000// Copyright 2021 Google LLC // 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 // https://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. //go:build integration // +build integration package main import ( "context" "fmt" "strings" "testing" ) const testIp = "192.168.0.0" func TestAddAndRemoveLocalRoute(t *testing.T) { metdata, err := getMetadata(context.Context(), false) if err != nil { t.Fatalf("failed to get metadata, err %v", err) } iface, err := getInterfaceByMAC(metdata.Instance.NetworkInterfaces[0].Mac) if err != nil { t.Fatalf("failed to get interface from mac, err %v", err) } // test add local route if err := removeLocalRoute(testIp, iface.Name); err != nil { t.Fatalf("failed to remove local route, err %v", err) } if err := addLocalRoute(testIp, iface.Name); err != nil { t.Fatalf("add test local route should not failed, err %v", err) } res, err := getLocalRoutes(iface.Name) if err != nil { t.Fatalf("get local route should not failed, err %v", err) } exist := false for _, route := range res { if strings.Contains(route, fmt.Sprintf("local %s/24", testIp)) { exist = true } } if !exist { t.Fatalf("route %s is not added", testIp) } // test remove local route if err := removeLocalRoute(testIp, iface.Name); err != nil { t.Fatalf("add test local route should not failed") } res, err := getLocalRoutes(iface.Name) if err != nil { t.Fatalf("ip route list should not failed, err %s", res.err) } for _, route := range res { if strings.Contains(route, fmt.Sprintf("local %s/24", testIp)) { t.Fatalf("route %s should be removed but exist", testIp) } } } func TestGetLocalRoute(t *testing.T) { metdata, err := getMetadata(context.Context(), false) if err != nil { t.Fatalf("failed to get metadata, err %v", err) } iface, err := getInterfaceByMAC(metdata.Instance.NetworkInterfaces[0].Mac) if err != nil { t.Fatalf("failed to get interface from mac, err %v", err) } if err := addLocalRoute(testIp, iface.Name); err != nil { t.Fatalf("add test local route should not failed, err %v", err) } routes, err := getLocalRoutes(iface.Name) if err != nil { t.Fatalf("get local routes should not failed, err %v", err) } if len(routes) != 1 { t.Fatal("find unexpected local route %s.", routes[0]) } if routes[0] != testIp { t.Fatal("find unexpected local route %s.", routes[0]) } } guest-agent-20231004.02/google_guest_agent/addresses_test.go000066400000000000000000000234601450737260700236360ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "context" "encoding/json" "fmt" "reflect" "testing" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/metadata" ) func reloadConfig(t *testing.T, extraDefaults []byte) { t.Helper() if err := cfg.Load(extraDefaults); err != nil { t.Fatalf("Error parsing config: %+v", err) } } func TestCompareRoutes(t *testing.T) { var tests = []struct { forwarded, metadata, wantAdd, wantRm []string }{ // These should return toAdd: // In Md, not present {nil, []string{"1.2.3.4"}, []string{"1.2.3.4"}, nil}, {nil, []string{"1.2.3.4", "5.6.7.8"}, []string{"1.2.3.4", "5.6.7.8"}, nil}, // These should return toRm: // Present, not in Md {[]string{"1.2.3.4"}, nil, nil, []string{"1.2.3.4"}}, {[]string{"1.2.3.4", "5.6.7.8"}, []string{"5.6.7.8"}, nil, []string{"1.2.3.4"}}, // These should return nil, nil: // Present, in Md {[]string{"1.2.3.4"}, []string{"1.2.3.4"}, nil, nil}, {[]string{"1.2.3.4", "5.6.7.8"}, []string{"1.2.3.4", "5.6.7.8"}, nil, nil}, {[]string{"1.2.3.4", "5.6.7.8"}, []string{"1.2.3.4", "5.6.7.8"}, nil, nil}, } for idx, tt := range tests { toAdd, toRm := compareRoutes(tt.forwarded, tt.metadata) if !reflect.DeepEqual(tt.wantAdd, toAdd) { t.Errorf("case %d: toAdd does not match expected: forwarded: %q, metadata: %q, got: %q, want: %q", idx, tt.forwarded, tt.metadata, toAdd, tt.wantAdd) } if !reflect.DeepEqual(tt.wantRm, toRm) { t.Errorf("case %d: toRm does not match expected: forwarded: %q, metadata: %q, got: %q, want: %q", idx, tt.forwarded, tt.metadata, toRm, tt.wantRm) } } } func TestAddressDisabled(t *testing.T) { var tests = []struct { name string data []byte md *metadata.Descriptor want bool }{ {"not explicitly disabled", []byte(""), &metadata.Descriptor{}, false}, {"enabled in cfg only", []byte("[addressManager]\ndisable=false"), &metadata.Descriptor{}, false}, {"disabled in cfg only", []byte("[addressManager]\ndisable=true"), &metadata.Descriptor{}, true}, {"disabled in cfg, enabled in instance metadata", []byte("[addressManager]\ndisable=true"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAddressManager: mkptr(false)}}}, true}, {"enabled in cfg, disabled in instance metadata", []byte("[addressManager]\ndisable=false"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAddressManager: mkptr(true)}}}, false}, {"enabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAddressManager: mkptr(false)}}}, false}, {"enabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{DisableAddressManager: mkptr(false)}}}, false}, {"disabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAddressManager: mkptr(true)}}}, true}, {"enabled in instance metadata, disabled in project metadata", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAddressManager: mkptr(false)}}, Project: metadata.Project{Attributes: metadata.Attributes{DisableAddressManager: mkptr(true)}}}, false}, {"disabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{DisableAddressManager: mkptr(true)}}}, true}, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reloadConfig(t, tt.data) newMetadata = tt.md got, err := (&addressMgr{}).Disabled(ctx) if err != nil { t.Errorf("Failed to run addressMgr's Disabled() call, got error: %+v", err) } if got != tt.want { t.Errorf("addressMgr.Disabled() got: %t, want: %t", got, tt.want) } }) } } func TestAddressDiff(t *testing.T) { var tests = []struct { name string data []byte md *metadata.Descriptor want bool }{ {"not set", []byte(""), &metadata.Descriptor{}, false}, {"enabled in cfg only", []byte("[wsfc]\nenable=true"), &metadata.Descriptor{}, true}, {"disabled in cfg only", []byte("[wsfc]\nenable=false"), &metadata.Descriptor{}, false}, {"disabled in cfg, enabled in instance metadata", []byte("[wsfc]\nenable=false"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableWSFC: mkptr(true)}}}, false}, {"enabled in cfg, disabled in instance metadata", []byte("[wsfc]\nenable=true"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableWSFC: mkptr(false)}}}, true}, {"enabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableWSFC: mkptr(true)}}}, true}, {"enabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{EnableWSFC: mkptr(true)}}}, true}, {"disabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableWSFC: mkptr(false)}}}, false}, {"enabled in instance metadata, disabled in project metadata", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableWSFC: mkptr(true)}}, Project: metadata.Project{Attributes: metadata.Attributes{EnableWSFC: mkptr(false)}}}, true}, {"disabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{EnableWSFC: mkptr(false)}}}, false}, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reloadConfig(t, tt.data) oldWSFCEnable = false oldMetadata = &metadata.Descriptor{} newMetadata = tt.md got, err := (&addressMgr{}).Diff(ctx) if err != nil { t.Errorf("Failed to run addressMgr's Diff() call, got error: %+v", err) } if got != tt.want { t.Errorf("addresses.diff() got: %t, want: %t", got, tt.want) } }) } } func TestWsfcFilter(t *testing.T) { var tests = []struct { metaDataJSON []byte expectedIps []string }{ // signle nic with enable-wsfc set to true {[]byte(`{"instance":{"attributes":{"enable-wsfc":"true"}, "networkInterfaces":[{"forwardedIps":["192.168.0.0", "192.168.0.1"]}]}}`), []string{}}, // multi nic with enable-wsfc set to true {[]byte(`{"instance":{"attributes":{"enable-wsfc":"true"}, "networkInterfaces":[{"forwardedIps":["192.168.0.0", "192.168.0.1"]},{"forwardedIps":["192.168.0.2"]}]}}`), []string{}}, // filter with wsfc-addrs {[]byte(`{"instance":{"attributes":{"wsfc-addrs":"192.168.0.1"}, "networkInterfaces":[{"forwardedIps":["192.168.0.0", "192.168.0.1"]}]}}`), []string{"192.168.0.0"}}, // filter with both wsfc-addrs and enable-wsfc flag {[]byte(`{"instance":{"attributes":{"wsfc-addrs":"192.168.0.1", "enable-wsfc":"true"}, "networkInterfaces":[{"forwardedIps":["192.168.0.0", "192.168.0.1"]}]}}`), []string{"192.168.0.0"}}, // filter with invalid wsfc-addrs {[]byte(`{"instance":{"attributes":{"wsfc-addrs":"192.168.0"}, "networkInterfaces":[{"forwardedIps":["192.168.0.0", "192.168.0.1"]}]}}`), []string{"192.168.0.0", "192.168.0.1"}}, } for i, tt := range tests { t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { var md metadata.Descriptor reloadConfig(t, nil) if err := json.Unmarshal(tt.metaDataJSON, &md); err != nil { t.Error("failed to unmarshal test JSON:", tt, err) } newMetadata = &md testAddress := addressMgr{} testAddress.applyWSFCFilter(cfg.Get()) forwardedIps := []string{} for _, ni := range newMetadata.Instance.NetworkInterfaces { forwardedIps = append(forwardedIps, ni.ForwardedIps...) } if !reflect.DeepEqual(forwardedIps, tt.expectedIps) { t.Errorf("wsfc filter failed: expect - %q, actual - %q", tt.expectedIps, forwardedIps) } }) } } func TestWsfcFlagTriggerAddressDiff(t *testing.T) { var tests = []struct { newMetadata, oldMetadata *metadata.Descriptor }{ // trigger diff on wsfc-addrs {&metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{WSFCAddresses: "192.168.0.1"}}}, &metadata.Descriptor{}}, {&metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{WSFCAddresses: "192.168.0.1"}}}, &metadata.Descriptor{}}, {&metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{WSFCAddresses: "192.168.0.1"}}}, &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{WSFCAddresses: "192.168.0.2"}}}}, {&metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{WSFCAddresses: "192.168.0.1"}}}, &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{WSFCAddresses: "192.168.0.2"}}}}, } ctx := context.Background() for i, tt := range tests { t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { reloadConfig(t, nil) oldWSFCAddresses = tt.oldMetadata.Instance.Attributes.WSFCAddresses newMetadata = tt.newMetadata oldMetadata = tt.oldMetadata testAddress := addressMgr{} diff, err := testAddress.Diff(ctx) if err != nil { t.Errorf("Failed to run addressMgr's Diff() call, got error: %+v", err) } if !diff { t.Errorf("old: %v new: %v doesn't trigger diff.", tt.oldMetadata, tt.newMetadata) } }) } } guest-agent-20231004.02/google_guest_agent/addresses_unix.go000066400000000000000000000025441450737260700236420ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. //go:build !windows // +build !windows package main import ( "errors" "net" ) // TODO: addLocalRoute and addRoute should be merged with the addition of ipForwardType to ipForwardEntry. func addIPForwardEntry(route ipForwardEntry) error { return errors.New("addIPForwardEntry unimplemented on non Windows systems") } // TODO: getLocalRoutes and getIPForwardEntries should be merged. func getIPForwardEntries() ([]ipForwardEntry, error) { return nil, errors.New("getIPForwardEntries unimplemented on non Windows systems") } func addAddress(ip net.IP, mask net.IPMask, index uint32) error { return errors.New("addAddress unimplemented on non Windows systems") } func removeAddress(ip net.IP, index uint32) error { return errors.New("removeAddress unimplemented on non Windows systems") } guest-agent-20231004.02/google_guest_agent/addresses_windows.go000066400000000000000000000220421450737260700243440ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. //go:build windows // +build windows package main import ( "bytes" "encoding/binary" "fmt" "net" "syscall" "unsafe" "golang.org/x/sys/windows" ) var ( ipHlpAPI = windows.NewLazySystemDLL("iphlpapi.dll") procAddIPAddress = ipHlpAPI.NewProc("AddIPAddress") procDeleteIPAddress = ipHlpAPI.NewProc("DeleteIPAddress") procCreateIpForwardEntry = ipHlpAPI.NewProc("CreateIpForwardEntry") procDeleteIpForwardEntry = ipHlpAPI.NewProc("DeleteIpForwardEntry") procGetIpForwardTable = ipHlpAPI.NewProc("GetIpForwardTable") procGetIpInterfaceEntry = ipHlpAPI.NewProc("GetIpInterfaceEntry") procSetIpInterfaceEntry = ipHlpAPI.NewProc("SetIpInterfaceEntry") procCreateUnicastIpAddressEntry = ipHlpAPI.NewProc("CreateUnicastIpAddressEntry") procInitializeUnicastIpAddressEntry = ipHlpAPI.NewProc("InitializeUnicastIpAddressEntry") procGetUnicastIpAddressEntry = ipHlpAPI.NewProc("GetUnicastIpAddressEntry") procDeleteUnicastIpAddressEntry = ipHlpAPI.NewProc("DeleteUnicastIpAddressEntry") ) const ( AF_NET = 2 AF_INET6 = 23 ) type MIB_IPFORWARD_TYPE DWORD const ( MIB_IPROUTE_TYPE_OTHER MIB_IPFORWARD_TYPE = 1 MIB_IPROUTE_TYPE_INVALID = 2 MIB_IPROUTE_TYPE_DIRECT = 3 MIB_IPROUTE_TYPE_INDIRECT = 4 ) type MIB_IPFORWARD_PROTO DWORD const MIB_IPPROTO_NETMGMT MIB_IPFORWARD_PROTO = 3 type ( in_addr struct { S_un struct { S_addr uint32 } } SOCKADDR_IN struct { sin_family int16 sin_addr in_addr } SOCKADDR_INET struct { Ipv4 SOCKADDR_IN si_family int16 } NET_LUID struct { Value uint64 Info struct { NetLuidIndex uint64 IfType uint64 } } MIB_UNICASTIPADDRESS_ROW struct { Address SOCKADDR_INET InterfaceLuid NET_LUID InterfaceIndex uint32 PrefixOrigin uint32 SuffixOrigin uint32 ValidLifetime uint32 PreferredLifetime uint32 OnLinkPrefixLength uint8 SkipAsSource bool } IF_INDEX DWORD MIB_IPFORWARDROW struct { dwForwardDest uint32 dwForwardMask uint32 dwForwardPolicy uint32 dwForwardNextHop uint32 dwForwardIfIndex IF_INDEX dwForwardType MIB_IPFORWARD_TYPE dwForwardProto MIB_IPFORWARD_PROTO dwForwardAge int32 dwForwardNextHopAS int32 dwForwardMetric1 int32 dwForwardMetric2 int32 dwForwardMetric3 int32 dwForwardMetric4 int32 dwForwardMetric5 int32 } ) func addAddress(ip net.IP, mask net.IPMask, index uint32) error { // CreateUnicastIpAddressEntry only available Vista onwards. if err := procCreateUnicastIpAddressEntry.Find(); err != nil { return addIPAddress(ip, mask, index) } subnet, _ := mask.Size() return createUnicastIpAddressEntry(ip, uint8(subnet), index) } func removeAddress(ip net.IP, index uint32) error { // DeleteUnicastIpAddressEntry only available Vista onwards. if err := procDeleteUnicastIpAddressEntry.Find(); err != nil { return deleteIPAddress(ip) } return deleteUnicastIpAddressEntry(ip, index) } func createUnicastIpAddressEntry(ip net.IP, prefix uint8, index uint32) error { ipRow := new(MIB_UNICASTIPADDRESS_ROW) // No return value. procInitializeUnicastIpAddressEntry.Call(uintptr(unsafe.Pointer(ipRow))) ipRow.InterfaceIndex = index ipRow.OnLinkPrefixLength = prefix // https://blogs.technet.microsoft.com/rmilne/2012/02/08/fine-grained-control-when-registering-multiple-ip-addresses-on-a-network-card/ ipRow.SkipAsSource = true ipRow.Address.si_family = AF_NET ipRow.Address.Ipv4.sin_family = AF_NET ipRow.Address.Ipv4.sin_addr.S_un.S_addr = binary.LittleEndian.Uint32(ip.To4()) if ret, _, _ := procCreateUnicastIpAddressEntry.Call(uintptr(unsafe.Pointer(ipRow))); ret != 0 { return fmt.Errorf("nonzero return code from CreateUnicastIpAddressEntry: %s", syscall.Errno(ret)) } return nil } func deleteUnicastIpAddressEntry(ip net.IP, index uint32) error { ipRow := new(MIB_UNICASTIPADDRESS_ROW) ipRow.InterfaceIndex = index ipRow.Address.si_family = AF_NET ipRow.Address.Ipv4.sin_family = AF_NET ipRow.Address.Ipv4.sin_addr.S_un.S_addr = binary.LittleEndian.Uint32(ip.To4()) ret, _, _ := procGetUnicastIpAddressEntry.Call(uintptr(unsafe.Pointer(ipRow))) // ERROR_NOT_FOUND if ret == 1168 { // This address was added by addIPAddress(), need to remove with deleteIPAddress() return deleteIPAddress(ip) } if ret != 0 { return fmt.Errorf("nonzero return code from GetUnicastIpAddressEntry: %s", syscall.Errno(ret)) } if ret, _, _ := procDeleteUnicastIpAddressEntry.Call(uintptr(unsafe.Pointer(ipRow))); ret != 0 { return fmt.Errorf("nonzero return code from DeleteUnicastIpAddressEntry: %s", syscall.Errno(ret)) } return nil } func addIPAddress(ip net.IP, mask net.IPMask, index uint32) error { var nteC int var nteI int ret, _, _ := procAddIPAddress.Call( uintptr(binary.LittleEndian.Uint32(ip.To4())), uintptr(binary.LittleEndian.Uint32(mask)), uintptr(index), uintptr(unsafe.Pointer(&nteC)), uintptr(unsafe.Pointer(&nteI))) if ret != 0 { return fmt.Errorf("nonzero return code from AddIPAddress: %s", syscall.Errno(ret)) } return nil } func deleteIPAddress(ip net.IP) error { ip = ip.To4() b := make([]byte, 1) ai := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) l := uint32(0) syscall.GetAdaptersInfo(ai, &l) b = make([]byte, int32(l)) ai = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) if err := syscall.GetAdaptersInfo(ai, &l); err != nil { return err } for ; ai != nil; ai = ai.Next { for ipl := &ai.IpAddressList; ipl != nil; ipl = ipl.Next { ipb := bytes.Trim(ipl.IpAddress.String[:], "\x00") if string(ipb) != ip.String() { continue } nteC := ipl.Context ret, _, _ := procDeleteIPAddress.Call(uintptr(nteC)) if ret != 0 { return fmt.Errorf("nonzero return code from DeleteIPAddress: %s", syscall.Errno(ret)) } return nil } } return fmt.Errorf("did not find address %s on system", ip) } func getIPForwardEntries() ([]ipForwardEntry, error) { buf := make([]byte, 1) size := uint32(len(buf)) // First call gets the size of MIB_IPFORWARDTABLE. procGetIpForwardTable.Call( uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)), 0, ) buf = make([]byte, size) if ret, _, _ := procGetIpForwardTable.Call( uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)), 0, ); ret != 0 { return nil, fmt.Errorf("nonzero return code from GetIpForwardTable: %s", syscall.Errno(ret)) } /* struct MIB_IPFORWARDTABLE { DWORD dwNumEntries; MIB_IPFORWARDROW table[ANY_SIZE]; } */ numEntries := *(*uint32)(unsafe.Pointer(&buf[0])) // Walk through the returned table for each entry. var fes []ipForwardEntry for i := uint32(0); i < numEntries; i++ { // Extract each MIB_IPFORWARDROW from MIB_IPFORWARDTABLE fr := *((*MIB_IPFORWARDROW)(unsafe.Pointer( (uintptr(unsafe.Pointer(&buf[0])) + unsafe.Sizeof(numEntries)) + (unsafe.Sizeof(MIB_IPFORWARDROW{}) * uintptr(i)), ))) fd := make([]byte, 4) binary.LittleEndian.PutUint32(fd, uint32(fr.dwForwardDest)) fm := make([]byte, 4) binary.LittleEndian.PutUint32(fm, uint32(fr.dwForwardMask)) nh := make([]byte, 4) binary.LittleEndian.PutUint32(nh, uint32(fr.dwForwardNextHop)) fe := ipForwardEntry{ ipForwardDest: net.IP(fd), ipForwardMask: net.IPMask(fm), ipForwardNextHop: net.IP(nh), ipForwardIfIndex: int32(fr.dwForwardIfIndex), ipForwardMetric1: fr.dwForwardMetric1, } fes = append(fes, fe) } return fes, nil } func addIPForwardEntry(fe ipForwardEntry) error { // https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-createipforwardentry fr := &MIB_IPFORWARDROW{ dwForwardDest: binary.LittleEndian.Uint32(fe.ipForwardDest.To4()), dwForwardMask: binary.LittleEndian.Uint32(fe.ipForwardMask), dwForwardPolicy: 0, // unused dwForwardNextHop: binary.LittleEndian.Uint32(fe.ipForwardNextHop.To4()), dwForwardIfIndex: IF_INDEX(fe.ipForwardIfIndex), dwForwardType: MIB_IPROUTE_TYPE_INDIRECT, // unused dwForwardProto: MIB_IPPROTO_NETMGMT, dwForwardAge: 0, // unused dwForwardNextHopAS: 0, // unused dwForwardMetric1: fe.ipForwardMetric1, dwForwardMetric2: -1, // unused dwForwardMetric3: -1, // unused dwForwardMetric4: -1, // unused dwForwardMetric5: -1, // unused } if ret, _, _ := procCreateIpForwardEntry.Call(uintptr(unsafe.Pointer(fr))); ret != 0 { return fmt.Errorf("nonzero return code from CreateIpForwardEntry: %s", syscall.Errno(ret)) } return nil } guest-agent-20231004.02/google_guest_agent/agentcrypto/000077500000000000000000000000001450737260700226255ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/agentcrypto/credentials/000077500000000000000000000000001450737260700251225ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/agentcrypto/credentials/guest_credentials.pb.go000066400000000000000000000136311450737260700315610ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.31.0 // protoc v3.21.12 // source: guest_credentials.proto package credentials import ( tpm "github.com/google/go-tpm-tools/proto/tpm" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // A server response containing client credentials. type GuestCredentialsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields EncryptedCredentials []byte `protobuf:"bytes,1,opt,name=encrypted_credentials,json=encryptedCredentials,proto3" json:"encrypted_credentials,omitempty"` KeyImportBlob *tpm.ImportBlob `protobuf:"bytes,2,opt,name=key_import_blob,json=keyImportBlob,proto3" json:"key_import_blob,omitempty"` } func (x *GuestCredentialsResponse) Reset() { *x = GuestCredentialsResponse{} if protoimpl.UnsafeEnabled { mi := &file_guest_credentials_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GuestCredentialsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GuestCredentialsResponse) ProtoMessage() {} func (x *GuestCredentialsResponse) ProtoReflect() protoreflect.Message { mi := &file_guest_credentials_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GuestCredentialsResponse.ProtoReflect.Descriptor instead. func (*GuestCredentialsResponse) Descriptor() ([]byte, []int) { return file_guest_credentials_proto_rawDescGZIP(), []int{0} } func (x *GuestCredentialsResponse) GetEncryptedCredentials() []byte { if x != nil { return x.EncryptedCredentials } return nil } func (x *GuestCredentialsResponse) GetKeyImportBlob() *tpm.ImportBlob { if x != nil { return x.KeyImportBlob } return nil } var File_guest_credentials_proto protoreflect.FileDescriptor var file_guest_credentials_proto_rawDesc = []byte{ 0x0a, 0x17, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x1a, 0x09, 0x74, 0x70, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x88, 0x01, 0x0a, 0x18, 0x47, 0x75, 0x65, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x15, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x14, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x37, 0x0a, 0x0f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x70, 0x6d, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x42, 0x0e, 0x5a, 0x0c, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_guest_credentials_proto_rawDescOnce sync.Once file_guest_credentials_proto_rawDescData = file_guest_credentials_proto_rawDesc ) func file_guest_credentials_proto_rawDescGZIP() []byte { file_guest_credentials_proto_rawDescOnce.Do(func() { file_guest_credentials_proto_rawDescData = protoimpl.X.CompressGZIP(file_guest_credentials_proto_rawDescData) }) return file_guest_credentials_proto_rawDescData } var file_guest_credentials_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_guest_credentials_proto_goTypes = []interface{}{ (*GuestCredentialsResponse)(nil), // 0: agentcrypto.credentials.GuestCredentialsResponse (*tpm.ImportBlob)(nil), // 1: tpm.ImportBlob } var file_guest_credentials_proto_depIdxs = []int32{ 1, // 0: agentcrypto.credentials.GuestCredentialsResponse.key_import_blob:type_name -> tpm.ImportBlob 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name } func init() { file_guest_credentials_proto_init() } func file_guest_credentials_proto_init() { if File_guest_credentials_proto != nil { return } if !protoimpl.UnsafeEnabled { file_guest_credentials_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GuestCredentialsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_guest_credentials_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_guest_credentials_proto_goTypes, DependencyIndexes: file_guest_credentials_proto_depIdxs, MessageInfos: file_guest_credentials_proto_msgTypes, }.Build() File_guest_credentials_proto = out.File file_guest_credentials_proto_rawDesc = nil file_guest_credentials_proto_goTypes = nil file_guest_credentials_proto_depIdxs = nil } guest-agent-20231004.02/google_guest_agent/agentcrypto/credentials/guest_credentials.proto000066400000000000000000000016671450737260700317250ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. syntax = "proto3"; package agentcrypto.credentials; option go_package = "/credentials"; // Specify import path to "github.com/google/go-tpm-tools/proto/tpm.proto" during compilation. import "tpm.proto"; // A server response containing client credentials. message GuestCredentialsResponse { bytes encrypted_credentials = 1; tpm.ImportBlob key_import_blob = 2; }guest-agent-20231004.02/google_guest_agent/agentcrypto/crypto_util.go000066400000000000000000000066711450737260700255430ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package agentcrypto import ( "crypto/ecdsa" "crypto/x509" "encoding/pem" "fmt" "os" "github.com/google/tink/go/aead/subtle" ) // parseCertificate validates certificate is in valid PEM format. func parseCertificate(cert []byte) (*x509.Certificate, error) { block, _ := pem.Decode(cert) if block == nil { return nil, fmt.Errorf("failed to parse PEM certificate") } x509Cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse certificate: %w", err) } return x509Cert, nil } // parsePvtKey validates the key is in valid format and returns the EC Private Key. func parsePvtKey(pemKey []byte) (*ecdsa.PrivateKey, error) { key, _ := pem.Decode(pemKey) if key == nil { return nil, fmt.Errorf("failed to decode PEM Key") } ecKey, err := x509.ParseECPrivateKey(key.Bytes) if err != nil { return nil, fmt.Errorf("failed to parse EC Private Key: %w", err) } return ecKey, nil } // serialNumber reads the certificate from file and returns the serial number in hex. func serialNumber(f string) (string, error) { d, err := os.ReadFile(f) if err != nil { return "", fmt.Errorf("unable to read previous client credential file %q: %w", f, err) } crt, err := parseCertificate(d) if err != nil { return "", fmt.Errorf("unable to parse certificate at %q: %w", f, err) } return fmt.Sprintf("%x", crt.SerialNumber), nil } // verifySign verifies the client certificate is valid and signed by root CA. func verifySign(cert []byte, rootCAFile string) error { caCertPEM, err := os.ReadFile(rootCAFile) if err != nil { return fmt.Errorf("failed to read CA PEM file for verifying signature: %w", err) } x509Cert, err := parseCertificate(cert) if err != nil { return fmt.Errorf("failed to parse client certificate for verifying signature: %w", err) } roots := x509.NewCertPool() if !roots.AppendCertsFromPEM(caCertPEM) { return fmt.Errorf("failed to add %q to new certpool for verifying client certificate", rootCAFile) } opts := x509.VerifyOptions{ Roots: roots, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } if _, err := x509Cert.Verify(opts); err != nil { return fmt.Errorf("failed to verify client certificate against root CA %q: %w", rootCAFile, err) } return nil } // encrypt encrypts plain text using AES GCM algorithm. func encrypt(aesKey []byte, plainText []byte, associatedData []byte) ([]byte, error) { cipher, err := subtle.NewAESGCM(aesKey) if err != nil { return nil, fmt.Errorf("failed to initialize cipher: %v", err) } return cipher.Encrypt(plainText, associatedData) } // decrypt decrypts AES GCM encrypted cipher text. func decrypt(aesKey []byte, cipherText []byte, associatedData []byte) ([]byte, error) { cipher, err := subtle.NewAESGCM(aesKey) if err != nil { return nil, fmt.Errorf("failed to initialize cipher: %v", err) } return cipher.Decrypt(cipherText, associatedData) } guest-agent-20231004.02/google_guest_agent/agentcrypto/crypto_util_test.go000066400000000000000000000223171450737260700265750ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package agentcrypto import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/pem" "os" "path/filepath" "testing" ) const validCertPEM = ` -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfRrObuSW5T7q 5CnSEqefEmtH4CCv6+5EckuriNr1CjfVvqzwfAhopXkLrq45EQm8vkmf7W96XJhC 7ZM0dYi1/qOCAU8wggFLMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAa BgNVHREEEzARgg9tYWlsLmdvb2dsZS5jb20wCwYDVR0PBAQDAgeAMGgGCCsGAQUF BwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29tL0dJQUcy LmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5jb20vb2Nz cDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/BAIwADAf BgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHSAEEDAOMAwGCisG AQQB1nkCBQEwMAYDVR0fBCkwJzAloCOgIYYfaHR0cDovL3BraS5nb29nbGUuY29t L0dJQUcyLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAH6RYHxHdcGpMpFE3oxDoFnP+ gtuBCHan2yE2GRbJ2Cw8Lw0MmuKqHlf9RSeYfd3BXeKkj1qO6TVKwCh+0HdZk283 TZZyzmEOyclm3UGFYe82P/iDFt+CeQ3NpmBg+GoaVCuWAARJN/KfglbLyyYygcQq 0SgeDh8dRKUiaW3HQSoYvTvdTuqzwK4CXsr3b5/dAOY8uMuG/IAR3FgwTbZ1dtoW RvOTa8hYiU6A475WuZKyEHcwnGYe57u2I2KbMgcKjPniocj4QzgYsVAVKW3IwaOh yE+vPxsiUkvQHdO2fojCkY8jg70jxM+gu59tPDNbw3Uh/2Ij310FgTHsnGQMyA== -----END CERTIFICATE-----` const invalidCertPEM = ` -----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw gtuBCHan2yE2GRbJ2Cw8Lw0MmuKqHlf9RSeYfd3BXeKkj1qO6TVKwCh+0HdZk283 TZZyzmEOyclm3UGFYe82P/iDFt+CeQ3NpmBg+GoaVCuWAARJN/KfglbLyyYygcQq yE+vPxsiUkvQHdO2fojCkY8jg70jxM+gu59tPDNbw3Uh/2Ij310FgTHsnGQMyA== -----END CERTIFICATE-----` func TestParseCertificate(t *testing.T) { if _, err := parseCertificate([]byte(validCertPEM)); err != nil { t.Errorf("parseCertificate(%s) failed unexpectedly with error: %v", validCertPEM, err) } } func TestParseCertificateError(t *testing.T) { if _, err := parseCertificate([]byte(invalidCertPEM)); err == nil { t.Errorf("parseCertificate(%s) succeeded unexpectedly for invalid certificate, want error", invalidCertPEM) } } func TestEncryptDecrypt(t *testing.T) { // 32 byte key. key := []byte("AES256Key-32Characters1234567890") plaintext := []byte("testplaintext") ciphertext, err := encrypt(key, plaintext, nil) if err != nil { t.Errorf("encrypt(%s,%s) failed unexpectedly with error: %v", key, plaintext, err) } got, err := decrypt(key, ciphertext, nil) if err != nil { t.Errorf("decrypt(%s,%s) failed unexpectedly with error: %v", string(key), string(ciphertext), err) } if !bytes.Equal(got, plaintext) { t.Errorf("decrypt(%s,%s) = %s want %s", string(key), string(ciphertext), string(got), string(plaintext)) } } const cacert = ` -----BEGIN CERTIFICATE----- MIIDbTCCAlWgAwIBAgIUFTF0rnA2LoffIJEKSh+rQcmehSIwDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzA3MjgyMjI3MTdaGA8zMDIy MTEyODIyMjcxN1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAKiWs/hXZgTtFkpFvdXO/nLpLJSCq5rwqAJauTmj Y78Za1QmgaqCcguakKf/hb+MxRL9h9qJVBAQkNZv0nChoTJyD6YF5hh4DDrQCPuh 1wvVsUhUllIbKsJbjQmdkOb3A5fMoe1ki4BLsr1CtJfJVj1+ifR+7hNkD3fW2sls XZlrNZRmbMKq84KRBWTSSxhjYZGd2cCGpecJ2fWuva9QhairdnB4TORAfjiyH+5v GEwXWC9gyDIIXWDG/kxwDDnh7kub0UsMf/neLv0hejpW/pfmvt32IoMaTEGFDaj7 lhTo7UVQw/XCFWqElsi8gHXR+/UdzbON5a8GiyjWJq5SThsCAwEAAaNTMFEwHQYD VR0OBBYEFPvD/mUJgRgzLmWCD5zFNglzMb55MB8GA1UdIwQYMBaAFPvD/mUJgRgz LmWCD5zFNglzMb55MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB ABUr0RNasEZ39wM1CDE/qZDo+gBMGWH8gE/x152KPvzvJZmI96LkYuKzmbIrvogJ rfGYkAP2LYc8bX6zs4e2VycF0pml7ARKHyinzDdcwXOKzg9gGanoZw4wXEtxfWSl GbmNplmhmMpEnrtTNeDbqGWvmO/1fziNduimNVVu1iltNYEszE/ch8AlMT7flfNm JnhzvUUnGeXDiWUIJdneDfXopatOboL/0HimnfNK6//NKUlMCQOfNbNND+372jhK B3V0o4sGyoh8/Jlas+SqEtVKv+jfNfAG0urLzJc4Zn2uc2chpZnD8DxkmzA5nJCf +5xLOukYO2I5KMgyYkYNUXs= -----END CERTIFICATE----- ` func TestVerifySign(t *testing.T) { // Fake self signed ceritificates for testing. client := ` -----BEGIN CERTIFICATE----- MIIDADCCAegCAQEwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 ZDAgFw0yMzA3MjgyMjI3NDZaGA8zMDIyMTEyODIyMjc0NlowRTELMAkGA1UEBhMC QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp dHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxAeYva w0J52P9e2IXPEyyJncOKOSGxCWqf0yHpuQ95STMMrgVSodN1Jdrpd2DPOqOYIriK uHw1L4DCm5/yGP7WznN/JOORoJTZ5qJXBXNNQZxf1d5qJeBWtFnVv2pAPwFM/c8j YNFCjTxAHjEMfZN0uXt1ELa6OkYCwxxiVq+Z6QT47xhvQHBzCFhPCaXy8ezvBanU m2AJ2O3HYu9JCy37baDsyVlhrt1qRTKG3JFCgqGEs2vkFo25ebv0Nq8crtT8J6wz YWbIpB56v+299f7jqStjljapG+nMrSbk8BRvMPAlg8Hg6mJ3RQgW5DgKE/7BSgue U2oF7ODsKjF5xesCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnbriOFcw2b/1zqfr M3FK3TAjcD+InKpY/bNjhdbfCRgO3WYdnWsVU437vKkiJH0tAOAzdR3Yd5xpLkuS uUjIiY4VTR10tZxmFuyAq87NZx4zMCJ7XNRQtDU5o+EUyZVV7l2fwbS31unCZQn5 10nSg5TQE3kZ4u+3x4PZgSbMhIY8P5Q/ZjAKVKk0hnT1ClQ5LQwetcDR7KMq9DpE sC6BD/ElCi0RJrZqVVccAhumf9NBk/qWf1E4njlmYLmqNrfZGEfbxiKAOsZFYaCV p/45VCE10OS3mYEFwJmQjH5NoqaSGxWU28reovEEmrDFoGYfkMQbxZzay0LURXt7 aSrX4A== -----END CERTIFICATE----- ` root := filepath.Join(t.TempDir(), "root.ca") if err := os.WriteFile(root, []byte(cacert), 0644); err != nil { t.Fatalf("Failed to setup test CA cert file: %v", err) } if err := verifySign([]byte(client), root); err != nil { t.Errorf("verifySign failed unexpectedly with error: %v", err) } } func TestVerifySignError(t *testing.T) { // Fake invalid self signed ceritificates for testing. client := ` -----BEGIN CERTIFICATE----- MIIDADCCAegCAQEwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 ZDAgFw0yMzA3MjgyMjM4NTBaGA8zMDIyMTEyODIyMzg1MFowRTELMAkGA1UEBhMC QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp dHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKW78MTO TO6F5/68B/e3qQYHRJ1OYv43+1U503fTnkQIyf1KtZvABmPIXmckDJAlTmtD8WQp lKVxCtSJ0aNNwj2epFBo/CoO5gIuFWjjxkiTfneCDxTF4SxqzVzvNuT0JtsG/Ysd 2b2GCIhHbqM7YLCol6V++SSO+NTR2kUx6RQ+f4vvnKWfv2pRgl8jHhq29U71BKtY k1rH6kd13QOl71IMY3E2SRB9rONe0/lgrVyaKKJto5a0WVDgrjZP4e+0lpvtD3jN JOFcJYrrDHAdxjQMEqbT4b1+M/HEOwJMDI2nZAI2exDmN8R2Wburp7hKNeygA4AM 7x91qP9jNfmS/wUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfv4sxcTTu66KU1h2 ol2DY2JQSywsWY37cfrdL9D1u2sf/MSyAN+i6XcwG/WReoPS8jLFPWJBVHYFQOWt OVw93lVfFlFfz1GojCiddGZxZTWLhKSVvnkRVuRlOD7ph6UjowTUe+JrK5bh/pT8 m+g/HmvC/0V5fgQFvtujjc3DkHzKk7HXj39OFsLVGvNDdI6f7+mdc7ib2qs5/uQt T+CR3W1LK08doMc8/SG74Q1i8eU1/AcX1QK1SQqX/TBF8EpCDII8BMTBp/KPp6JV GPQpdL4CXXRtVxz5wf/GuMKbgBe9nPh9bFoRrmH6B/LK9dckvZJG9wT7lzuCXZ3d zBbQ2g== -----END CERTIFICATE----- ` root := filepath.Join(t.TempDir(), "root.ca") if err := os.WriteFile(root, []byte(cacert), 0644); err != nil { t.Fatalf("Failed to setup test CA cert file: %v", err) } tests := []struct { name string client string }{ { name: "invalid_signed_client", client: client, }, { name: "incorrectly_formatted_client", client: invalidCertPEM, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if err := verifySign([]byte(test.client), root); err == nil { t.Errorf("verifySign succeeded unexpectedly for %s, want error", test.name) } }) } } func TestSerialNumber(t *testing.T) { f := filepath.Join(t.TempDir(), "cert") if err := os.WriteFile(f, []byte(validCertPEM), 0777); err != nil { t.Errorf("Failed to create test cert file: %v", err) } want := "137d4565568f5d35" got, err := serialNumber(f) if err != nil { t.Errorf("serialNumber(%s) failed unexpectedly with error: %v", f, err) } if got != want { t.Errorf("serialNumber(%s) = %s, want %s", f, got, want) } } func generatePrivateKey(t *testing.T) (*ecdsa.PrivateKey, []byte) { t.Helper() key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { t.Fatalf("Failed to generate key: %v", err) } x509Encoded, err := x509.MarshalECPrivateKey(key) if err != nil { t.Fatalf("Failed to Marshal EC PrivateKey: %v", err) } return key, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509Encoded}) } func TestParseECPrivateKey(t *testing.T) { key, pem := generatePrivateKey(t) got, err := parsePvtKey(pem) if err != nil { t.Errorf("parsePvtKey(%s) failed unexpectedly with error: %v", string(pem), err) } if !key.Equal(got) { t.Errorf("parsePvtKey(%s) parsed private key incorrectly", string(pem)) } } guest-agent-20231004.02/google_guest_agent/agentcrypto/mtls_mds.go000066400000000000000000000166761450737260700250160ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package agentcrypto provides various cryptography related utility functions for Guest Agent. package agentcrypto import ( "context" "fmt" "path/filepath" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/uefi" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/google/go-tpm-tools/client" "github.com/google/go-tpm-tools/proto/tpm" "github.com/google/go-tpm/legacy/tpm2" "google.golang.org/protobuf/encoding/protojson" pb "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/agentcrypto/credentials" ) const ( // UEFI variables are of format {VariableName}-{VendorGUID} // googleGUID is Google's (vendors/variable owners) GUID used to prevent name collision with other vendors. googleGUID = "8be4df61-93ca-11d2-aa0d-00e098032b8c" // googleRootCACertEFIVarName is predefined string part of the UEFI variable name that holds Root CA cert. googleRootCACertEFIVarName = "InstanceRootCACertificate" // clientCertsKey is the metadata server key at which client identity certificate is exposed. clientCertsKey = "instance/credentials/certs" // MTLSSchedulerID is the identifier used by job scheduler. MTLSSchedulerID = "MTLS_MDS_Credential_Boostrapper" // MTLSScheduleInterval is interval at which credential bootstrapper runs. MTLSScheduleInterval = 48 * time.Hour ) var ( googleRootCACertUEFIVar = uefi.VariableName{Name: googleRootCACertEFIVarName, GUID: googleGUID} ) // CredsJob implements job scheduler interface for generating/rotating credentials. type CredsJob struct { client metadata.MDSClientInterface } // New initializer new job. func New() *CredsJob { return &CredsJob{ client: metadata.New(), } } // readRootCACert reads Root CA cert from UEFI variable. func (j *CredsJob) readRootCACert(name uefi.VariableName) (*uefi.Variable, error) { rootCACert, err := uefi.ReadVariable(name) if err != nil { return nil, fmt.Errorf("unable to read root CA cert file contents: %w", err) } if _, err := parseCertificate(rootCACert.Content); err != nil { return nil, fmt.Errorf("unable to verify Root CA cert: %w", err) } logger.Infof("Successfully read root CA Cert from %+v", name) return rootCACert, nil } // getClientCredentials fetches encrypted credentials from MDS and unmarshal it into GuestCredentialsResponse. func (j *CredsJob) getClientCredentials(ctx context.Context) (*pb.GuestCredentialsResponse, error) { creds, err := j.client.GetKey(ctx, clientCertsKey, nil) if err != nil { return nil, fmt.Errorf("unable to get client credentials from MDS: %w", err) } res := &pb.GuestCredentialsResponse{} if err := protojson.Unmarshal([]byte(creds), res); err != nil { return nil, fmt.Errorf("unable to unmarshal MDS response(%+v): %w", creds, err) } return res, nil } // extractKey decrypts the key cipher text (Key encryption Key encrypted Data Dencryption Key) // through vTPM and returns the key (DEK) as plain text. func (j *CredsJob) extractKey(importBlob *tpm.ImportBlob) ([]byte, error) { rwc, err := tpm2.OpenTPM() if err != nil { return nil, fmt.Errorf("unable to open a channel to the TPM: %w", err) } defer rwc.Close() ek, err := client.EndorsementKeyECC(rwc) if err != nil { return nil, fmt.Errorf("failed to load a key from TPM: %w", err) } defer ek.Close() dek, err := ek.Import(importBlob) if err != nil { return nil, fmt.Errorf("failed to decrypt import blob: %w", err) } return dek, nil } // fetchClientCredentials fetches encrypted client credentials from MDS, // extracts Key Encryption Key (KEK) from vTPM, decrypts the client credentials using KEK, // and verifies that the certificate is signed by root CA. func (j *CredsJob) fetchClientCredentials(ctx context.Context, rootCA string) ([]byte, error) { resp, err := j.getClientCredentials(ctx) if err != nil { return []byte{}, err } dek, err := j.extractKey(resp.GetKeyImportBlob()) if err != nil { return []byte{}, err } plaintext, err := decrypt(dek, resp.GetEncryptedCredentials(), nil) if err != nil { return []byte{}, err } if err := verifySign(plaintext, rootCA); err != nil { return []byte{}, err } return plaintext, nil } // Run generates the required credentials for MTLS MDS workflow. // // 1. Fetches, verifies and writes Root CA cert from UEFI variable to /run/google-mds-mtls/root.crt // 2. Fetches encrypted client credentials from MDS, decrypts it via vTPM and writes it to /run/google-mds-mtls/client.key // // Note that these credentials are at `C:\Program Files\Google\Compute Engine\certs\mds` on Windows. // Additionally agent also generates a PFX file on windows that can be used invoking HTTPS endpoint. // // Example usage of these credentials to call HTTPS endpoint of MDS: // // curl --cacert /run/google-mds-mtls/root.crt -E /run/google-mds-mtls/client.key -H "MetadataFlavor: Google" https://169.254.169.254 // // Windows example: // // $cert = Get-PfxCertificate -FilePath "C:\ProgramData\Google\Compute Engine\mds-mtls-client.key.pfx" // or // $cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Issuer -like "*google.internal*" } // Invoke-RestMethod -Uri https://169.254.169.254 -Method Get -Headers @{"Metadata-Flavor"="Google"} -Certificate $cert func (j *CredsJob) Run(ctx context.Context) (bool, error) { logger.Infof("Fetching Root CA cert...") v, err := j.readRootCACert(googleRootCACertUEFIVar) if err != nil { return true, fmt.Errorf("failed to read Root CA cert with an error: %w", err) } if err := j.writeRootCACert(ctx, v.Content, filepath.Join(defaultCredsDir, rootCACertFileName)); err != nil { return true, fmt.Errorf("failed to store Root CA cert with an error: %w", err) } logger.Infof("Fetching client credentials...") creds, err := j.fetchClientCredentials(ctx, filepath.Join(defaultCredsDir, rootCACertFileName)) if err != nil { return true, fmt.Errorf("failed to generate client credentials with an error: %w", err) } if err := j.writeClientCredentials(creds, filepath.Join(defaultCredsDir, clientCredsFileName)); err != nil { return true, fmt.Errorf("failed to store client credentials with an error: %w", err) } logger.Infof("Successfully bootstrapped MDS mTLS credentials") return true, nil } // ID returns the ID for this job. func (j *CredsJob) ID() string { return MTLSSchedulerID } // Interval returns the interval at which job is executed. func (j *CredsJob) Interval() (time.Duration, bool) { return MTLSScheduleInterval, true } // ShouldEnable returns true if MDS endpoint for fetching credentials is available on the VM. // Used for identifying if we want schedule bootstrapping and enable MDS mTLS credential rotation. func (j *CredsJob) ShouldEnable(ctx context.Context) bool { _, err := j.client.GetKey(ctx, clientCertsKey, nil) if err != nil { logger.Warningf("Skipping scheduling credential generation job, failed to reach client credentials endpoint(%s) with error: %v", clientCertsKey, err) return false } return true } guest-agent-20231004.02/google_guest_agent/agentcrypto/mtls_mds_linux.go000066400000000000000000000072221450737260700262200ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package agentcrypto import ( "context" "fmt" "os/exec" "path/filepath" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) const ( // defaultCredsDir is the directory location for MTLS MDS credentials. defaultCredsDir = "/run/google-mds-mtls" // rootCACertFileName is the root CA cert. rootCACertFileName = "root.crt" // clientCredsFileName are client credentials, its basically the file // that has the EC private key and the client certificate concatenated. clientCredsFileName = "client.key" ) // writeRootCACert writes Root CA cert from UEFI variable to output file. func (j *CredsJob) writeRootCACert(ctx context.Context, content []byte, outputFile string) error { if err := utils.SaferWriteFile(content, outputFile, 0644); err != nil { return err } // Best effort to update system store, don't fail. if err := updateSystemStore(ctx, outputFile); err != nil { logger.Errorf("Failed add Root MDS cert to system trust store with error: %v", err) } return nil } // writeClientCredentials stores client credentials (certificate and private key). func (j *CredsJob) writeClientCredentials(plaintext []byte, outputFile string) error { return utils.SaferWriteFile(plaintext, outputFile, 0644) } // getCAStoreUpdater interates over known system trust store updaters and returns the first found. func getCAStoreUpdater() (string, error) { knownUpdaters := []string{"update-ca-certificates", "update-ca-trust"} var errs []string for _, u := range knownUpdaters { _, err := exec.LookPath(u) if err == nil { return u, nil } errs = append(errs, err.Error()) } return "", fmt.Errorf("no known trust updaters %v were found: %v", knownUpdaters, errs) } // certificateDirFromUpdater returns directory of local CA certificates for the given updater tool. func certificateDirFromUpdater(updater string) (string, error) { switch updater { // SUSE, Debian and Ubuntu distributions. // https://manpages.ubuntu.com/manpages/xenial/man8/update-ca-certificates.8.html case "update-ca-certificates": return "/usr/local/share/ca-certificates/", nil // CentOS, Fedora, RedHat distributions. // https://www.unix.com/man-page/centos/8/UPDATE-CA-TRUST/ case "update-ca-trust": return "/etc/pki/ca-trust/source/anchors/", nil default: return "", fmt.Errorf("unknown updater %q, no local trusted CA certificate directory found", updater) } } // updateSystemStore updates the local system store with the cert. func updateSystemStore(ctx context.Context, cert string) error { cmd, err := getCAStoreUpdater() if err != nil { return err } dir, err := certificateDirFromUpdater(cmd) if err != nil { return err } dest := filepath.Join(dir, filepath.Base(cert)) if err := utils.CopyFile(cert, dest, 0644); err != nil { return err } res := run.WithOutput(ctx, cmd) if res.ExitCode != 0 { return fmt.Errorf("command %q failed with error: %s", cmd, res.Error()) } logger.Infof("Certificate %q added to system store successfully %s", cert, res.StdOut) return nil } guest-agent-20231004.02/google_guest_agent/agentcrypto/mtls_mds_linux_test.go000066400000000000000000000112731450737260700272600ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package agentcrypto import ( "context" "os" "path/filepath" "testing" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/fakes" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/uefi" ) func TestReadAndWriteRootCACert(t *testing.T) { root := t.TempDir() v := uefi.VariableName{Name: "testname", GUID: "testguid", RootDir: root} j := &CredsJob{} fakeUefi := []byte("attr" + validCertPEM) path := filepath.Join(root, "testname-testguid") if err := os.WriteFile(path, fakeUefi, 0644); err != nil { t.Fatalf("Failed to write test file: %v", err) } defer os.Remove(path) crt := filepath.Join(root, "root.crt") ca, err := j.readRootCACert(v) if err != nil { t.Errorf("readRootCACert(%+v) failed unexpectedly with error: %v", v, err) } if err := j.writeRootCACert(context.Background(), ca.Content, crt); err != nil { t.Errorf("writeRootCACert(%s, %s) failed unexpectedly with error: %v", string(ca.Content), crt, err) } got, err := os.ReadFile(crt) if err != nil { t.Errorf("Failed to read expected root cert file: %v", err) } if string(got) != validCertPEM { t.Errorf("readAndWriteRootCACert(%+v, %s) = %s, want %s", v, crt, string(got), validCertPEM) } } func TestReadAndWriteRootCACertError(t *testing.T) { root := t.TempDir() v := uefi.VariableName{Name: "not", GUID: "exist", RootDir: root} j := &CredsJob{} // Non-existent UEFI variable. if _, err := j.readRootCACert(v); err == nil { t.Errorf("readRootCACert(%+v) succeeded unexpectedly for non-existent UEFI variable, want error", v) } // Invalid PEM certificate. fakeUefi := []byte("attr" + invalidCertPEM) path := filepath.Join(root, "testname-testguid") if err := os.WriteFile(path, fakeUefi, 0644); err != nil { t.Fatalf("Failed to write test file: %v", err) } defer os.Remove(path) if _, err := j.readRootCACert(v); err == nil { t.Errorf("readRootCACert(%+v) succeeded unexpectedly for invalid PEM certificate, want error", v) } } func TestGetClientCredentials(t *testing.T) { ctx := context.WithValue(context.Background(), fakes.MDSOverride, "succeed") j := &CredsJob{ client: fakes.NewFakeMDSClient(), } if _, err := j.getClientCredentials(ctx); err != nil { t.Errorf("getClientCredentials(ctx, client) failed unexpectedly with error: %v", err) } } func TestGetClientCredentialsError(t *testing.T) { ctx := context.Background() j := &CredsJob{ client: fakes.NewFakeMDSClient(), } tests := []string{"fail_mds_connect", "fail_unmarshal"} for _, test := range tests { t.Run(test, func(t *testing.T) { ctx = context.WithValue(ctx, fakes.MDSOverride, test) if _, err := j.getClientCredentials(ctx); err == nil { t.Errorf("getClientCredentials(ctx, client) succeeded for %s, want error", test) } }) } } func TestShouldEnable(t *testing.T) { ctx := context.WithValue(context.Background(), fakes.MDSOverride, "succeed") j := &CredsJob{ client: fakes.NewFakeMDSClient(), } if !j.ShouldEnable(ctx) { t.Error("ShouldEnable(ctx) = false, want true") } } func TestShouldEnableError(t *testing.T) { ctx := context.WithValue(context.Background(), fakes.MDSOverride, "fail_mds_connect") j := &CredsJob{ client: fakes.NewFakeMDSClient(), } if j.ShouldEnable(ctx) { t.Error("ShouldEnable(ctx) = true, want false") } } func TestCertificateDirFromUpdater(t *testing.T) { tests := []struct { updater string want string }{ { updater: "update-ca-certificates", want: "/usr/local/share/ca-certificates/", }, { updater: "update-ca-trust", want: "/etc/pki/ca-trust/source/anchors/", }, } for _, test := range tests { t.Run(test.updater, func(t *testing.T) { got, err := certificateDirFromUpdater(test.updater) if err != nil { t.Errorf("certificateDirFromUpdater(%s) failed unexpectedly with error: %v", test.updater, err) } if got != test.want { t.Errorf("certificateDirFromUpdater(%s) = %s, want %s", test.updater, got, test.want) } }) } } func TestCertificateDirFromUpdaterError(t *testing.T) { _, err := certificateDirFromUpdater("unknown") if err == nil { t.Errorf("certificateDirFromUpdater(unknown) succeeded for unknown updater, want error") } } guest-agent-20231004.02/google_guest_agent/agentcrypto/mtls_mds_windows.go000066400000000000000000000237161450737260700265610ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package agentcrypto import ( "context" "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "os" "path/filepath" "syscall" "unsafe" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "golang.org/x/sys/windows" "software.sslmate.com/src/go-pkcs12" ) const ( // rootCACertFileName is the root CA cert. rootCACertFileName = "mds-mtls-root.crt" // clientCredsFileName are client credentials, its basically the file // that has the EC private key and the client certificate concatenated. clientCredsFileName = "mds-mtls-client.key" // pfxFile stores client credentials in PFX format. pfxFile = "mds-mtls-client.key.pfx" // https://learn.microsoft.com/en-us/windows/win32/seccrypto/system-store-locations // my is predefined personal cert store. my = "MY" // root is predefined cert store for root trusted CA certs. root = "ROOT" // certificateIssuer is the issuer of client/root certificates for MDS mTLS. certificateIssuer = "google.internal" // maxCertEnumeration specifies the maximum number of times to search for a certificate // with a serial number from a given issuer before giving up. maxCertEnumeration = 5 ) var ( // defaultCredsDir is the directory location for MTLS MDS credentials. defaultCredsDir = filepath.Join(os.Getenv("ProgramData"), "Google", "Compute Engine") prevCtx *windows.CertContext ) // writeRootCACert writes Root CA cert from UEFI variable to output file. func (j *CredsJob) writeRootCACert(_ context.Context, cacert []byte, outputFile string) error { if err := utils.SaferWriteFile(cacert, outputFile, 0644); err != nil { return err } x509Cert, err := parseCertificate(cacert) if err != nil { return fmt.Errorf("failed to parse root CA cert: %w", err) } // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certcreatecertificatecontext certContext, err := windows.CertCreateCertificateContext( windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, &x509Cert.Raw[0], uint32(len(x509Cert.Raw))) if err != nil { return fmt.Errorf("CertCreateCertificateContext returned: %v", err) } // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfreecertificatecontext defer windows.CertFreeCertificateContext(certContext) // Adds certificate to Root Trusted certificates. if err := addCtxToLocalSystemStore(root, certContext, uint32(windows.CERT_STORE_ADD_REPLACE_EXISTING)); err != nil { return fmt.Errorf("failed to store root cert ctx in store: %w", err) } return nil } // findCert finds and returns certificate issued by issuer with the serial number in the given the store. func findCert(storeName, issuer, certID string) (*windows.CertContext, error) { logger.Infof("Searching for certificate with serial number %s in store %s by issuer %s", certID, storeName, issuer) st, err := windows.CertOpenStore( windows.CERT_STORE_PROV_SYSTEM, 0, 0, windows.CERT_SYSTEM_STORE_LOCAL_MACHINE, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(storeName)))) if err != nil { return nil, fmt.Errorf("failed to open cert store: %w", err) } defer windows.CertCloseStore(st, 0) // prev is used for enumerating through all the certificates that matches the issuer. // On the first call to the function this parameter is NULL on all subsequent calls, // this parameter is the last CertContext pointer returned by the CertFindCertificateInStore function var prev *windows.CertContext // maxCertEnumeration would avoid requiring a infinite loop that relies on enumerating // until we get nil crt. for i := 1; i <= maxCertEnumeration; i++ { logger.Debugf("Attempt %d, searching certificate...", i) // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore crt, err := windows.CertFindCertificateInStore( st, windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, 0, windows.CERT_FIND_ISSUER_STR, unsafe.Pointer(syscall.StringToUTF16Ptr(issuer)), prev) if err != nil { return nil, fmt.Errorf("unable to find certificate: %w", err) } if crt == nil { return nil, fmt.Errorf("no certificate by issuer %s with ID %s", issuer, certID) } x509Cert, err := certContextToX509(crt) if err != nil { return nil, fmt.Errorf("failed to parse certificate context: %w", err) } if fmt.Sprintf("%x", x509Cert.SerialNumber) == certID { return crt, nil } prev = crt } return nil, nil } // writeClientCredentials stores client credentials (certificate and private key). func (j *CredsJob) writeClientCredentials(creds []byte, outputFile string) error { num, err := serialNumber(outputFile) if err != nil { logger.Warningf("Could not get previous serial number, will skip cleanup: %v", err) } if err := utils.SaferWriteFile(creds, outputFile, 0644); err != nil { return fmt.Errorf("failed to write client key: %w", err) } pfx, err := generatePFX(creds) if err != nil { return fmt.Errorf("failed to generate PFX data from client credentials: %w", err) } p := filepath.Join(filepath.Dir(outputFile), pfxFile) if err := utils.SaferWriteFile(pfx, p, 0644); err != nil { return fmt.Errorf("failed to write PFX file: %w", err) } blob := windows.CryptDataBlob{ Size: uint32(len(pfx)), Data: &pfx[0], } // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-pfximportcertstore handle, err := windows.PFXImportCertStore(&blob, syscall.StringToUTF16Ptr(""), windows.CRYPT_MACHINE_KEYSET) if err != nil { return fmt.Errorf("failed to import PFX in cert store: %w", err) } defer windows.CertCloseStore(handle, 0) var crtCtx *windows.CertContext // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certenumcertificatesinstore crtCtx, err = windows.CertEnumCertificatesInStore(handle, crtCtx) if err != nil { return fmt.Errorf("failed to get cert context for PFX from store: %w", err) } defer windows.CertFreeCertificateContext(crtCtx) // Add certificate to personal store. if err := addCtxToLocalSystemStore(my, crtCtx, uint32(windows.CERT_STORE_ADD_NEWER)); err != nil { return fmt.Errorf("failed to store pfx cert context: %w", err) } // Search for previous certificate if its not already in memory. if prevCtx == nil && num != "" { prevCtx, err = findCert(my, certificateIssuer, num) if err != nil { logger.Warningf("Failed to find previous certificate with error: %v", err) } } // Remove previous certificate only after successful refresh. if err := deleteCert(prevCtx, my); err != nil { logger.Warningf("Failed to delete previous certificate(%s) with error: %v", num, err) } prevCtx = windows.CertDuplicateCertificateContext(crtCtx) return nil } // certContextToX509 creates an x509 Certificate from a Windows cert context. func certContextToX509(ctx *windows.CertContext) (*x509.Certificate, error) { der := unsafe.Slice(ctx.EncodedCert, int(ctx.Length)) return x509.ParseCertificate(der) } // generatePFX accepts certificate concatenated with private key and generates a PFX out of it. // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/personal-information-exchange---pfx--files func generatePFX(creds []byte) (pfxData []byte, err error) { cert, key := pem.Decode(creds) x509Cert, err := x509.ParseCertificate(cert.Bytes) if err != nil { return []byte{}, fmt.Errorf("failed to parse client certificate: %w", err) } ecpvt, err := parsePvtKey(key) if err != nil { return []byte{}, fmt.Errorf("failed to parse EC PrivateKey from client credentials: %w", err) } return pkcs12.Encode(rand.Reader, ecpvt, x509Cert, nil, "") } func addCtxToLocalSystemStore(storeName string, certContext *windows.CertContext, disposition uint32) error { // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certopenstore // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/local-machine-and-current-user-certificate-stores // https://learn.microsoft.com/en-us/windows/win32/seccrypto/system-store-locations#cert_system_store_local_machine st, err := windows.CertOpenStore( windows.CERT_STORE_PROV_SYSTEM, 0, 0, windows.CERT_SYSTEM_STORE_LOCAL_MACHINE, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(storeName)))) if err != nil { return fmt.Errorf("failed to open cert store: %w", err) } // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certclosestore defer windows.CertCloseStore(st, 0) // https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certaddcertificatecontexttostore if err := windows.CertAddCertificateContextToStore(st, certContext, disposition, nil); err != nil { return fmt.Errorf("failed to add certificate context to store: %w", err) } return nil } func deleteCert(crtCtx *windows.CertContext, storeName string) error { if crtCtx == nil { return nil } st, err := windows.CertOpenStore( windows.CERT_STORE_PROV_SYSTEM, 0, 0, windows.CERT_SYSTEM_STORE_LOCAL_MACHINE, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(storeName)))) if err != nil { return fmt.Errorf("failed to open cert store: %w", err) } defer windows.CertCloseStore(st, 0) var dlCtx *windows.CertContext dlCtx, err = windows.CertFindCertificateInStore( st, windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING, 0, windows.CERT_FIND_EXISTING, unsafe.Pointer(crtCtx), dlCtx, ) if err != nil { return fmt.Errorf("unable to find the certificate in %q store to delete: %w", storeName, err) } return windows.CertDeleteCertificateFromStore(dlCtx) } guest-agent-20231004.02/google_guest_agent/cfg/000077500000000000000000000000001450737260700210255ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/cfg/cfg.go000066400000000000000000000257521450737260700221260ustar00rootroot00000000000000// Copyright 2023 Google Inc. All Rights Reserved. // // 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. // Package cfg is package responsible to loading and accessing the guest environment configuration. package cfg import ( "fmt" "runtime" "github.com/go-ini/ini" ) var ( // instance is the single instance of configuration sections, once loaded this package // should always return it. instance *Sections // dataSource is a pointer to a data source loading/defining function, unit tests will // want to change this pointer to whatever makes sense to its implementation. dataSources = defaultDataSources ) const ( winConfigPath = `C:\Program Files\Google\Compute Engine\instance_configs.cfg` unixConfigPath = `/etc/default/instance_configs.cfg` defaultConfig = ` [Accounts] deprovision_remove = false gpasswd_add_cmd = gpasswd -a {user} {group} gpasswd_remove_cmd = gpasswd -d {user} {group} groupadd_cmd = groupadd {group} groups = adm,dip,docker,lxd,plugdev,video reuse_homedir = false useradd_cmd = useradd -m -s /bin/bash -p * {user} userdel_cmd = userdel -r {user} [Daemons] accounts_daemon = true clock_skew_daemon = true network_daemon = true [IpForwarding] ethernet_proto_id = 66 ip_aliases = true target_instance_ips = true [Instance] instance_id = instance_id_dir = /etc/google_instance_id [InstanceSetup] host_key_dir = /etc/ssh host_key_types = ecdsa,ed25519,rsa network_enabled = true optimize_local_ssd = true set_boto_config = true set_host_keys = true set_multiqueue = true [MetadataScripts] default_shell = /bin/bash run_dir = shutdown = true shutdown-windows = true startup = true startup-windows = true sysprep-specialize = true [NetworkInterfaces] dhcp_command = ip_forwarding = true setup = true [OSLogin] cert_authentication = true [Snapshots] enabled = false snapshot_service_ip = 169.254.169.254 snapshot_service_port = 8081 timeout_in_seconds = 60 [Unstable] mds_mtls = false ` ) // Sections encapsulates all the configuration sections. type Sections struct { // AccountManager defines the address management configurations. It takes precedence over instance's // and project's metadata configuration. The default configuration doesn't define values to it, if the // user has defined it then we shouldn't even consider metadata values. Users must check if this // pointer is nil or not. AccountManager *AccountManager `ini:"accountManager,omitempty"` // Accounts defines the non windows account management options, behaviors and commands. Accounts *Accounts `ini:"Accounts,omitempty"` // AddressManager defines the address management configurations. It takes precedence over instance's // and project's metadata configuration. The default configuration doesn't define values to it, if the // user has defined it then we shouldn't even consider metadata values. Users must check if this // pointer is nil or not. AddressManager *AddressManager `ini:"addressManager,omitempty"` // Daemons defines the availability of clock skew, network and account managers. Daemons *Daemons `ini:"Daemons,omitempty"` // Diagnostics defines the diagnostics configurations. It takes precedence over instance's // and project's metadata configuration. The default configuration doesn't define values to it, if the // user has defined it then we shouldn't even consider metadata values. Users must check if this // pointer is nil or not. Diagnostics *Diagnostics `ini:"diagnostics,omitempty"` // IPForwarding defines the ip forwarding configuration options. IPForwarding *IPForwarding `ini:"IpForwarding,omitempty"` // Instance defines the instance ID handling behaviors, i.e. where to read the ID from etc. Instance *Instance `ini:"Instance,omitempty"` // InstanceSetup defines options to basic instance setup options i.e. optimize local ssd, network, // host keys etc. InstanceSetup *InstanceSetup `ini:"InstanceSetup,omitempty"` // MetadataScripts contains the configurations of the metadata-scripts service. MetadataScripts *MetadataScripts `ini:"MetadataScripts,omitempty"` // NetworkInterfaces defines if the network interfaces should be managed/configured by guest-agent // as well as the commands definitions for network configuration. NetworkInterfaces *NetworkInterfaces `ini:"NetworkInterfaces,omitempty"` // OSLogin defines the OS Login configuration options. OSLogin *OSLogin `ini:"OSLogin,omitempty"` // Snpashots defines the snapshot listener configuration and behavior i.e. the server address and port. Snapshots *Snapshots `ini:"Snapshots,omitempty"` // Unstable is a "under development feature flags" section. No stability or long term support is // guaranteed for any keys under this section. No application, script or utility should rely on it. Unstable *Unstable `ini:"Unstable,omitempty"` // WSFC defines the wsfc configurations. It takes precedence over instance's and project's // metadata configuration. The default configuration doesn't define values to it, if the user // has defined it then we shouldn't even consider metadata values. Users must check if this // pointer is nil or not. WSFC *WSFC `ini:"wsfc,omitempty"` } // AccountManager contains the configurations of AccountManager section. type AccountManager struct { Disable bool `ini:"disable,omitempty"` } // Accounts contains the configurations of Accounts section. type Accounts struct { DeprovisionRemove bool `ini:"deprovision_remove,omitempty"` GPasswdAddCmd string `ini:"gpasswd_add_cmd,omitempty"` GPasswdRemoveCmd string `ini:"gpasswd_remove_cmd,omitempty"` GroupAddCmd string `ini:"groupadd_cmd,omitempty"` Groups string `ini:"groups,omitempty"` ReuseHomedir bool `ini:"reuse_homedir,omitempty"` UserAddCmd string `ini:"useradd_cmd,omitempty"` UserDelCmd string `ini:"userdel_cmd,omitempty"` } // AddressManager contains the configuration of addressManager section. type AddressManager struct { Disable bool `ini:"disable,omitempty"` } // Daemons contains the configurations of Daemons section. type Daemons struct { AccountsDaemon bool `ini:"accounts_daemon,omitempty"` ClockSkewDaemon bool `ini:"clock_skew_daemon,omitempty"` NetworkDaemon bool `ini:"network_daemon,omitempty"` } // Diagnostics contains the configurations of Diagnostics section. type Diagnostics struct { Enable bool `ini:"enable,omitempty"` } // IPForwarding contains the configurations of IPForwarding section. type IPForwarding struct { EthernetProtoID string `ini:"ethernet_proto_id,omitempty"` IPAliases bool `ini:"ip_aliases,omitempty"` TargetInstanceIPs bool `ini:"target_instance_ips,omitempty"` } // Instance contains the configurations of Instance section. type Instance struct { // InstanceID is a backward compatible key. In the past the instance id was only // supported/setup via config file, if we can't read the instance_id file then // try honoring this configuration key. InstanceID string `ini:"instance_id,omitempty"` // InstanceIDDir defines where the instance id file should be read from. InstanceIDDir string `ini:"instance_id_dir,omitempty"` } // InstanceSetup contains the configurations of InstanceSetup section. type InstanceSetup struct { HostKeyDir string `ini:"host_key_dir,omitempty"` HostKeyTypes string `ini:"host_key_types,omitempty"` NetworkEnabled bool `ini:"network_enabled,omitempty"` OptimizeLocalSSD bool `ini:"optimize_local_ssd,omitempty"` SetBotoConfig bool `ini:"set_boto_config,omitempty"` SetHostKeys bool `ini:"set_host_keys,omitempty"` SetMultiqueue bool `ini:"set_multiqueue,omitempty"` } // MetadataScripts contains the configurations of MetadataScripts section. type MetadataScripts struct { DefaultShell string `ini:"default_shell,omitempty"` RunDir string `ini:"run_dir,omitempty"` Shutdown bool `ini:"shutdown,omitempty"` ShutdownWindows bool `ini:"shutdown-windows,omitempty"` Startup bool `ini:"startup,omitempty"` StartupWindows bool `ini:"startup-windows,omitempty"` SysprepSpecialize bool `ini:"sysprep_specialize,omitempty"` } // OSLogin contains the configurations of OSLogin section. type OSLogin struct { CertAuthentication bool `ini:"cert_authentication,omitempty"` } // NetworkInterfaces contains the configurations of NetworkInterfaces section. type NetworkInterfaces struct { DHCPCommand string `ini:"dhcp_command,omitempty"` IPForwarding bool `ini:"ip_forwarding,omitempty"` Setup bool `ini:"setup,omitempty"` } // Snapshots contains the configurations of Snapshots section. type Snapshots struct { Enabled bool `ini:"enabled,omitempty"` SnapshotServiceIP string `ini:"snapshot_service_ip,omitempty"` SnapshotServicePort int `ini:"snapshot_service_port,omitempty"` TimeoutInSeconds int `ini:"timeout_in_seconds,omitempty"` } // Unstable contains the configurations of Unstable section. No long term stability or support // is guaranteed for configurations defined in the Unstable section. By default all flags defined // in this section is disabled and is intended to isolate under development features. type Unstable struct { MDSMTLS bool `ini:"mds_mtls,omitempty"` } // WSFC contains the configurations of WSFC section. type WSFC struct { Addresses string `ini:"addresses,omitempty"` Enable bool `ini:"enable,omitempty"` Port string `ini:"port,omitempty"` } func defaultConfigFile(osName string) string { if osName == "windows" { return winConfigPath } return unixConfigPath } func defaultDataSources(extraDefaults []byte) []interface{} { var res []interface{} configFile := defaultConfigFile(runtime.GOOS) if len(extraDefaults) > 0 { res = append(res, extraDefaults) } return append(res, []interface{}{ []byte(defaultConfig), configFile, configFile + ".distro", configFile + ".template", }...) } // Load loads default configuration and the configuration from default config files. func Load(extraDefaults []byte) error { opts := ini.LoadOptions{ Loose: true, Insensitive: true, } sources := dataSources(extraDefaults) cfg, err := ini.LoadSources(opts, sources[0], sources[1:]...) if err != nil { return fmt.Errorf("failed to load configuration: %+v", err) } sections := new(Sections) if err := cfg.MapTo(sections); err != nil { return fmt.Errorf("failed to map configuration to object: %+v", err) } instance = sections return nil } // Get returns the configuration's instance previously loaded with Load(). func Get() *Sections { if instance == nil { panic("cfg package was not initialized, Load() " + "should be called in the early initialization code path") } return instance } guest-agent-20231004.02/google_guest_agent/cfg/cfg_test.go000066400000000000000000000052131450737260700231530ustar00rootroot00000000000000// Copyright 2023 Google Inc. All Rights Reserved. // // 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. package cfg import "testing" func TestLoad(t *testing.T) { if err := Load(nil); err != nil { t.Fatalf("Failed to load configuration: %+v", err) } cfg := Get() if cfg.WSFC != nil { t.Errorf("WSFC shouldn't not be defined by default configuration, expected: nil, got: non-nil") } if cfg.Accounts.DeprovisionRemove == true { t.Errorf("Expected Accounts.deprovision_remove to be: false, got: true") } } func TestInvalidConfig(t *testing.T) { invalidConfig := ` [Section key = value ` dataSources = func(extraDefaults []byte) []interface{} { return []interface{}{ []byte(invalidConfig), } } // After testing set it back to the default one. defer func() { dataSources = defaultDataSources }() if err := Load(nil); err == nil { t.Errorf("Load() didn't fail to load invalid configuration, expected error") } } func TestDefaultDataSources(t *testing.T) { expectedDataSources := 4 sources := defaultDataSources(nil) if len(sources) != expectedDataSources { t.Errorf("defaultDataSources() returned wrong number of sources, expected: %d, got: %d", expectedDataSources, len(sources)) } _, ok := sources[0].([]byte) if !ok { t.Errorf("defaultDataSources() returned wrong sources, first source should be of type []byte") } } func TestDefaultConfigFile(t *testing.T) { windowsConfig := `C:\Program Files\Google\Compute Engine\instance_configs.cfg` unixConfig := `/etc/default/instance_configs.cfg` if got := defaultConfigFile("windows"); got != windowsConfig { t.Errorf("defaultConfigFile(windows) returned wrong file, expected: %s, got: %s", windowsConfig, got) } if got := defaultConfigFile("linux"); got != unixConfig { t.Errorf("defaultConfigFile(linux) returned wrong file, expected: %s, got: %s", unixConfig, got) } } func TestGetTwice(t *testing.T) { if err := Load(nil); err != nil { t.Fatalf("Failed to load configuration: %+v", err) } firstCfg := Get() secondCfg := Get() if firstCfg != secondCfg { t.Errorf("Get() should return always the same pointer, expected: %p, got: %p", firstCfg, secondCfg) } } guest-agent-20231004.02/google_guest_agent/clock.go000066400000000000000000000036431450737260700217160ustar00rootroot00000000000000// Copyright 2019 Google LLC // 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 // https://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. package main import ( "context" "runtime" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) type clockskewMgr struct{} func (a *clockskewMgr) Diff(ctx context.Context) (bool, error) { return oldMetadata.Instance.VirtualClock.DriftToken != newMetadata.Instance.VirtualClock.DriftToken, nil } func (a *clockskewMgr) Timeout(ctx context.Context) (bool, error) { return false, nil } func (a *clockskewMgr) Disabled(ctx context.Context) (bool, error) { enabled := cfg.Get().Daemons.ClockSkewDaemon return runtime.GOOS == "windows" || !enabled, nil } func (a *clockskewMgr) Set(ctx context.Context) error { if runtime.GOOS == "freebsd" { err := run.Quiet(ctx, "service", "ntpd", "status") if err == nil { if err := run.Quiet(ctx, "service", "ntpd", "stop"); err != nil { return err } defer func() { if err := run.Quiet(ctx, "service", "ntpd", "start"); err != nil { logger.Warningf("Error starting 'ntpd' after clock sync: %v.", err) } }() } // TODO get server return run.Quiet(ctx, "ntpdate", "169.254.169.254") } res := run.WithOutput(ctx, "/sbin/hwclock", "--hctosys", "-u", "--noadjfile") if res.ExitCode != 0 || res.StdErr != "" { return error(res) } return nil } guest-agent-20231004.02/google_guest_agent/diagnostics.go000066400000000000000000000075761450737260700231430ustar00rootroot00000000000000// Copyright 2018 Google LLC // 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 // https://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. package main import ( "context" "encoding/json" "reflect" "runtime" "sync/atomic" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) const diagnosticsCmd = `C:\Program Files\Google\Compute Engine\diagnostics\diagnostics.exe` var ( diagnosticsRegKey = "Diagnostics" diagnosticsDisabled = false // Indicate whether an existing job is runing to collect logs // 0 -> not running, 1 -> running isDiagnosticsRunning int32 = 0 ) type diagnosticsEntry struct { SignedURL string ExpireOn string Trace bool } type diagnosticsMgr struct { // fakeWindows forces Disabled to run as if it was running in a windows system. // mostly target for unit tests. fakeWindows bool } func (d *diagnosticsMgr) Diff(ctx context.Context) (bool, error) { return !reflect.DeepEqual(newMetadata.Instance.Attributes.Diagnostics, oldMetadata.Instance.Attributes.Diagnostics), nil } func (d *diagnosticsMgr) Timeout(ctx context.Context) (bool, error) { return false, nil } func (d *diagnosticsMgr) Disabled(ctx context.Context) (bool, error) { var disabled bool config := cfg.Get() if !d.fakeWindows && runtime.GOOS != "windows" { return true, nil } defer func() { if disabled != diagnosticsDisabled { diagnosticsDisabled = disabled logStatus("diagnostics", disabled) } }() // Diagnostics are opt-in and enabled by default. if config.Diagnostics != nil { return !config.Diagnostics.Enable, nil } if newMetadata.Instance.Attributes.EnableDiagnostics != nil { return !*newMetadata.Instance.Attributes.EnableDiagnostics, nil } if newMetadata.Project.Attributes.EnableDiagnostics != nil { return !*newMetadata.Project.Attributes.EnableDiagnostics, nil } return diagnosticsDisabled, nil } func (d *diagnosticsMgr) Set(ctx context.Context) error { logger.Infof("Diagnostics: logs export requested.") diagnosticsEntries, err := readRegMultiString(regKeyBase, diagnosticsRegKey) if err != nil && err != errRegNotExist { return err } strEntry := newMetadata.Instance.Attributes.Diagnostics if utils.ContainsString(strEntry, diagnosticsEntries) { return nil } diagnosticsEntries = append(diagnosticsEntries, strEntry) var entry diagnosticsEntry if err := json.Unmarshal([]byte(strEntry), &entry); err != nil { return err } expired, _ := utils.CheckExpired(entry.ExpireOn) if entry.SignedURL == "" || expired { return nil } args := []string{ "-signedUrl", entry.SignedURL, } if entry.Trace { args = append(args, "-trace") } // If no existing running job, set it to 1 and block other requests if !atomic.CompareAndSwapInt32(&isDiagnosticsRunning, 0, 1) { logger.Infof("Diagnostics: reject the request, as an existing process is collecting logs from the system") return nil } go func() { logger.Infof("Diagnostics: collecting logs from the system.") res := run.WithCombinedOutput(ctx, diagnosticsCmd, args...) logger.Infof(res.Combined) if res.ExitCode != 0 { logger.Warningf("Error collecting logs: %v", res.Error()) } // Job is done, unblock the following requests atomic.SwapInt32(&isDiagnosticsRunning, 0) }() return writeRegMultiString(regKeyBase, diagnosticsRegKey, diagnosticsEntries) } guest-agent-20231004.02/google_guest_agent/diagnostics_test.go000066400000000000000000000067461450737260700242000ustar00rootroot00000000000000// Copyright 2018 Google LLC // 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 // https://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. package main import ( "context" "testing" "time" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-agent/utils" ) func TestDiagnosticsEntryExpired(t *testing.T) { var tests = []struct { sTime string e bool }{ {time.Now().Add(5 * time.Minute).Format(time.RFC3339), false}, {time.Now().Add(-5 * time.Minute).Format(time.RFC3339), true}, {"some bad time", true}, } for _, tt := range tests { k := diagnosticsEntry{ExpireOn: tt.sTime} expired, _ := utils.CheckExpired(k.ExpireOn) if tt.e != expired { t.Errorf("diagnosticsEntry.expired() with ExpiredOn %q should return %t", k.ExpireOn, tt.e) } } } func TestDiagnosticsDisabled(t *testing.T) { var tests = []struct { name string data []byte md *metadata.Descriptor want bool }{ {"not explicitly enabled", []byte(""), &metadata.Descriptor{}, false}, {"enabled in cfg only", []byte("[diagnostics]\nenable=true"), &metadata.Descriptor{}, false}, {"disabled in cfg only", []byte("[diagnostics]\nenable=false"), &metadata.Descriptor{}, true}, {"disabled in cfg, enabled in instance metadata", []byte("[diagnostics]\nenable=false"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(true)}}}, true}, {"enabled in cfg, disabled in instance metadata", []byte("[diagnostics]\nenable=true"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(false)}}}, false}, {"enabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(true)}}}, false}, {"enabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(true)}}}, false}, {"disabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(false)}}}, true}, {"enabled in instance metadata, disabled in project metadata", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(true)}}, Project: metadata.Project{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(false)}}}, false}, {"disabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{EnableDiagnostics: mkptr(false)}}}, true}, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reloadConfig(t, tt.data) newMetadata = tt.md mgr := diagnosticsMgr{ fakeWindows: true, } got, err := mgr.Disabled(ctx) if err != nil { t.Errorf("Failed to run diagnosticsMgr's Disable() call: %+v", err) } if got != tt.want { t.Errorf("test case %q, diagnostics.disabled() got: %t, want: %t", tt.name, got, tt.want) } }) } } guest-agent-20231004.02/google_guest_agent/events/000077500000000000000000000000001450737260700215725ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/events/Readme.md000066400000000000000000000154001450737260700233110ustar00rootroot00000000000000# Guest Agent Events Handling Layer ## Overview The Guest Agent events handling layer is a generic and multi-purpose events handling layer, it's designed to offer a unified mechanism and API to manage and communicate "external" events across the guest agent implementation. Such "external" events could be a underlying Operating System event such as file changes or removal, sockets, named pipes etc as well as platform services such as metadata service, snapshot service etc. The events layer is formed of a **Manager**, a **Watcher** and a **Subscriber** where the **Manager** is the events controller/manager itself, the **Watcher** is the implementation of the event listening and the **Subscriber** is the callback function interested in a given event and registered to handle it or "to be notified when they happen". Each **Event** is internally identified by a string ID, when registering the **Subscriber** must tell what event it's interested on, such as: ```golang eventManager.Subscribe("metadata-watcher,longpoll", &userData, func(evType string, data interface{}, evData interface{}) bool { // Event handling implementation... return true }) ``` The **Subscriber** implementation must return a boolean, such a boolean determines if the **Subscriber** must be renewed or if it must be unregistered/unsubscribed. ## Sequence Diagram Below is a high level sequence diagram showing how the **Guest Agent**, **Manager**, **Watchers** and **Handlers/Subscribers** interact with each other: ``` ┌───────────┐ ┌─────────────┐ ┌─────────┐┌─────────┐┌─────────┐┌─────────┐ │Guest Agent│ │Event Manager│ │Watcher A││Watcher B││Handler A││Handler B│ └─────┬─────┘ └──────┬──────┘ └────┬────┘└────┬────┘└────┬────┘└────┬────┘ │ │ │ │ │ │ │ Initialize │ │ │ │ │ │────────────────>│ │ │ │ │ │ │ │ │ │ │ │ │ Register │ │ │ │ │ │─────────────>│ │ │ │ │ │ │ │ │ │ │ │ Register │ │ │ │ │────────────────────────>│ │ │ │ │ │ │ │ │ │Done initializing│ │ │ │ │ │<────────────────│ │ │ │ │ │ │ │ │ │ │ │ │ Subscribe()│ │ │ │ │─────────────────────────────────────────────────────>│ │ │ │ │ │ │ │ │ │ Subscribe() │ │ │ │────────────────────────────────────────────────────────────────>│ │ │ │ │ │ │ │ Run() │ │ │ │ │ │────────────────>│ │ │ │ │ │ │ │ │ │ │ │ │ Run() │ │ │ │ │ │─────────────>│ │ │ │ │ │ │ │ │ │ │ │ Run() │ │ │ │ │────────────────────────>│ │ │ │ │ │ │ │ │ │ │dispatch event│ │ │ │ │ │<─────────────│ │ │ │ │ │ │ │ │ │ │ │ │Call() │ │ │ │ │───────────────────────────────────>│ │ │ │ │ │ │ │ │ │ dispatch event │ │ │ │ │<────────────────────────│ │ │ │ │ │ │ │ │ │ │ │ Call() │ │ │ │──────────────────────────────────────────────>│ ┌─────┴─────┐ ┌──────┴──────┐ ┌────┴────┐┌────┴────┐┌────┴────┐┌────┴────┐ │Guest Agent│ │Event Manager│ │Watcher A││Watcher B││Handler A││Handler B│ └───────────┘ └─────────────┘ └─────────┘└─────────┘└─────────┘└─────────┘ ``` ## Built-in Watchers |Watcher|Events|Desc| |-------|------|----| |metadata|metadata-watcher,longpoll|A new version of the metadata descriptor was detected.| |ssh-trusted-ca-pipe-watcher|ssh-trusted-ca-pipe-watcher,read|A read in the trusted-ca pipe was detected.| guest-agent-20231004.02/google_guest_agent/events/events.go000066400000000000000000000223761450737260700234370ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package events is a events processing layer. package events import ( "context" "fmt" "sync" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/metadata" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/sshtrustedca" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) var ( // availableWatchers mapps all kown available event watchers. availableWatchers = make(map[string]Watcher) ) // Watcher defines the interface between the events manager and the actual // watcher implementation. type Watcher interface { // ID returns the watcher id. ID() string // Events return a slice with all the event types a given Watcher handles. Events() []string // Run implements the actuall "listening" strategy and emits a event "signal". // It must return: // - [bool] if the watcher should renew(run again). // - [interface{}] a event context data pointer further describing the event(if needed). // - [err] error case the Watcher failed and wants to notify subscribers(see EventData). Run(ctx context.Context, evType string) (bool, interface{}, error) } // Manager defines the interface between events management layer and the // core guest agent implementation. type Manager struct { watchers map[string]Watcher subscribers map[string][]*eventSubscriber } // Config offers a mechanism for the consumers to configure the Manager behavior. type Config struct { // Watchers lists the enabled watchers, of not provided all available watchers will be enabled. Watchers []string } // EventData wraps the data communicated from a Watcher to a Subscriber. type EventData struct { // Data points to the Watcher provided data. Data interface{} // Error is used when a Watcher has failed and wants communicate its subscribers about the error. Error error } // WatcherEventType wraps/couples together a Watcher and an event type. type WatcherEventType struct { // watcher is the watcher implementation for a given event type. watcher Watcher // evType idenfities the event type this object refences to. evType string } // EventCb defines the callback interface between watchers and subscribers. The arguments are: // - ctx the app' context passed in from the manager's Run() call. // - evType a string defining the what event type triggered the call. // - data a user context pointer to be consumed by the callback. // - evData a event specific data pointer. // // The callback should return true if it wants to renew, returning false will case the callback // to be unregistered/unsubscribed. type EventCb func(ctx context.Context, evType string, data interface{}, evData *EventData) bool type eventSubscriber struct { data interface{} cb EventCb } type eventBusData struct { evType string data *EventData } func init() { err := initWatchers([]Watcher{ metadata.New(), sshtrustedca.New(sshtrustedca.DefaultPipePath), }) if err != nil { logger.Errorf("Failed to initialize watchers: %+v", err) } } // init initializes the known available event watchers. func initWatchers(watchers []Watcher) error { for _, curr := range watchers { // Error if we are accidentaly not properly setting the id. if curr.ID() == "" { return fmt.Errorf("invalid event watcher id, skipping") } availableWatchers[curr.ID()] = curr } return nil } // New allocates and initializes a events Manager based on provided cfg. func New(cfg *Config) (*Manager, error) { res := &Manager{ watchers: availableWatchers, subscribers: make(map[string][]*eventSubscriber), } // Align manager's config based on consumers provided watchers If it is // passing in wanted/expected watchers, otherwise use all available ones. if cfg != nil && len(cfg.Watchers) > 0 { res.watchers = make(map[string]Watcher) for _, curr := range cfg.Watchers { // Report back if we don't know the provided watcher id. if _, found := availableWatchers[curr]; !found { return nil, fmt.Errorf("invalid/unknown watcher id: %s", curr) } res.watchers[curr] = availableWatchers[curr] } } return res, nil } // Subscribe registers an event consumer/subscriber callback to a given event type, data // is a context pointer provided by the caller to be passed down when calling cb when // a new event happens. func (mngr *Manager) Subscribe(evType string, data interface{}, cb EventCb) { mngr.subscribers[evType] = append(mngr.subscribers[evType], &eventSubscriber{ data: data, cb: cb, }, ) } func (mngr *Manager) eventTypes() []*WatcherEventType { var res []*WatcherEventType for _, watcher := range mngr.watchers { for _, evType := range watcher.Events() { res = append(res, &WatcherEventType{watcher, evType}) } } return res } type watcherQueue struct { mutex sync.Mutex watchersMap map[string]bool } func (ep *watcherQueue) add(evType string) { ep.mutex.Lock() defer ep.mutex.Unlock() ep.watchersMap[evType] = true } func (ep *watcherQueue) del(evType string) int { ep.mutex.Lock() defer ep.mutex.Unlock() delete(ep.watchersMap, evType) return len(ep.watchersMap) } // Run runs the event manager, it will block until all watchers have given up/failed. func (mngr *Manager) Run(ctx context.Context) { var wg sync.WaitGroup var leaving bool syncBus := make(chan eventBusData) defer close(syncBus) cancelContext := make(chan bool) cancelCallback := make(chan bool) defer close(cancelContext) defer close(cancelCallback) // Manages the context's done signal, pass it down to the other go routines to // finish its job and leave. Additionally, if the remaining go routines are leaving // we get it handled via syncBus channel and drop this go routine as well. wg.Add(1) go func(done <-chan struct{}, cancelContext <-chan bool, cancelCallback chan<- bool) { defer wg.Done() for { select { case <-done: logger.Debugf("Got context's Done() signal, leaving.") leaving = true cancelCallback <- true return case <-cancelContext: leaving = true return } } }(ctx.Done(), cancelContext, cancelCallback) // Manages the event processing avoiding blocking the watcher's go routines. // This will listen to syncBus and call the events handlers/callbacks. wg.Add(1) go func(bus <-chan eventBusData, cancelCallback <-chan bool) { defer wg.Done() for { select { case <-cancelCallback: return case busData := <-bus: subscribers, found := mngr.subscribers[busData.evType] if !found || len(subscribers) == 0 { logger.Debugf("No subscriber found for event: %s, returning.", busData.evType) continue } keepMe := make([]*eventSubscriber, 0) for _, curr := range mngr.subscribers[busData.evType] { logger.Debugf("Running registered callback for event: %s", busData.evType) renew := curr.cb(ctx, busData.evType, curr.data, busData.data) if renew { keepMe = append(keepMe, curr) } logger.Debugf("Returning from event %q subscribed callback, should renew?: %t", busData.evType, renew) } mngr.subscribers[busData.evType] = keepMe // No more subscribers for this event type, delete it from the subscribers map. if len(keepMe) == 0 { logger.Debugf("No more subscribers left for evType: %s", busData.evType) delete(mngr.subscribers, busData.evType) } // No more subscribers at all, we have nothing more left to do here. if len(mngr.subscribers) == 0 { logger.Debugf("No subscribers left, leaving") break } } } }(syncBus, cancelCallback) // This control struct manages the registered watchers, when it gets to len() // down to zero means all watchers are done and we can signal the other 2 control // go routines to leave(given we don't have any more job left to process). control := &watcherQueue{ watchersMap: make(map[string]bool), } // Creates a goroutine for each registered watcher's event and keep handling its // execution until they give up/finishes their job by returning renew = false. for _, curr := range mngr.eventTypes() { control.add(curr.evType) wg.Add(1) go func(bus chan<- eventBusData, watcher Watcher, evType string, cancelContext chan<- bool, cancelCallback chan<- bool) { var evData interface{} var err error defer wg.Done() for renew := true; renew; { renew, evData, err = watcher.Run(ctx, evType) logger.Debugf("Watcher(%s) returned event: %q, should renew?: %t", watcher.ID(), evType, renew) if leaving { break } bus <- eventBusData{ evType: evType, data: &EventData{ Data: evData, Error: err, }, } } if !leaving && control.del(evType) == 0 { logger.Debugf("All watchers are finished, signaling to leave.") cancelContext <- true cancelCallback <- true } }(syncBus, curr.watcher, curr.evType, cancelContext, cancelCallback) } wg.Wait() } guest-agent-20231004.02/google_guest_agent/events/events_test.go000066400000000000000000000207241450737260700244710ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package events import ( "context" "fmt" "testing" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/metadata" ) func TestConstructor(t *testing.T) { tests := []struct { config *Config success bool }{ {config: nil, success: true}, {config: &Config{Watchers: []string{metadata.WatcherID}}, success: true}, {config: &Config{Watchers: []string{"foobar"}}, success: false}, } for i, tt := range tests { t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { _, err := New(tt.config) if err != nil && tt.success { t.Errorf("expected success, got error: %+v", err) } }) } } func TestInitWatcers(t *testing.T) { tests := []struct { watchers []Watcher success bool }{ {watchers: []Watcher{metadata.New()}, success: true}, {watchers: []Watcher{&testWatcher{}}, success: false}, } for i, tt := range tests { t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { err := initWatchers(tt.watchers) if err != nil && tt.success { t.Errorf("expected success, got error: %+v", err) } }) } } type testWatcher struct { watcherID string counter int maxCount int } func (tprod *testWatcher) ID() string { return tprod.watcherID } func (tprod *testWatcher) Events() []string { return []string{tprod.watcherID + ",test-event"} } func (tprod *testWatcher) Run(ctx context.Context, evType string) (bool, interface{}, error) { tprod.counter++ evData := tprod.counter if tprod.counter >= tprod.maxCount { return false, nil, nil } return true, &evData, nil } func TestRun(t *testing.T) { watcherID := "test-watcher" maxCount := 10 err := initWatchers([]Watcher{ &testWatcher{ watcherID: watcherID, maxCount: maxCount, }, }) if err != nil { t.Fatalf("Failed to init/register watcher: %+v", err) } eventManager, err := New(&Config{Watchers: []string{watcherID}}) if err != nil { t.Fatalf("Failed to init event manager: %+v", err) } counter := 0 eventManager.Subscribe("test-watcher,test-event", &counter, func(ctx context.Context, evType string, data interface{}, evData *EventData) bool { dd := data.(*int) *dd++ return true }) eventManager.Run(context.Background()) if counter != maxCount { t.Errorf("Failed to increment callback counter, expected: %d, got: %d", maxCount, counter) } } func TestUnsubscribe(t *testing.T) { watcherID := "test-watcher" maxCount := 10 unsubscribeAt := 2 err := initWatchers([]Watcher{ &testWatcher{ watcherID: watcherID, maxCount: maxCount, }, }) if err != nil { t.Fatalf("Failed to init/register watcher: %+v", err) } eventManager, err := New(&Config{Watchers: []string{watcherID}}) if err != nil { t.Fatalf("Failed to init event manager: %+v", err) } counter := 0 eventManager.Subscribe("test-watcher,test-event", nil, func(ctx context.Context, evType string, data interface{}, evData *EventData) bool { if counter == unsubscribeAt { return false } counter++ return true }) eventManager.Run(context.Background()) if counter != unsubscribeAt { t.Errorf("Failed to unsubscribe callback, expected: %d, got: %d", unsubscribeAt, counter) } } func TestCancelBeforeCallbacks(t *testing.T) { watcherID := "test-watcher" timeout := (1 * time.Second) / 100 err := initWatchers([]Watcher{ &testCancel{ watcherID: watcherID, timeout: timeout, }, }) if err != nil { t.Fatalf("Failed to init/register watcher: %+v", err) } eventManager, err := New(&Config{Watchers: []string{watcherID}}) if err != nil { t.Fatalf("Failed to init event manager: %+v", err) } eventManager.Subscribe("test-watcher,test-event", nil, func(ctx context.Context, evType string, data interface{}, evData *EventData) bool { t.Errorf("Expected to have canceled before calling callback") return true }) ctx, cancel := context.WithCancel(context.Background()) go func() { time.Sleep(timeout / 2) cancel() }() eventManager.Run(ctx) } type testCancel struct { watcherID string timeout time.Duration } func (tc *testCancel) ID() string { return tc.watcherID } func (tc *testCancel) Events() []string { return []string{tc.watcherID + ",test-event"} } func (tc *testCancel) Run(ctx context.Context, evType string) (bool, interface{}, error) { time.Sleep(tc.timeout) return true, nil, nil } func TestCancelAfterCallbacks(t *testing.T) { watcherID := "test-watcher" timeout := (1 * time.Second) / 100 err := initWatchers([]Watcher{ &testCancel{ watcherID: watcherID, timeout: timeout, }, }) if err != nil { t.Fatalf("Failed to init/register watcher: %+v", err) } eventManager, err := New(&Config{Watchers: []string{watcherID}}) if err != nil { t.Fatalf("Failed to init event manager: %+v", err) } eventManager.Subscribe("test-watcher,test-event", nil, func(ctx context.Context, evType string, data interface{}, evData *EventData) bool { return true }) ctx, cancel := context.WithCancel(context.Background()) go func() { time.Sleep(timeout * 10) cancel() }() eventManager.Run(ctx) } type testCancelWatcher struct { watcherID string after int } func (tc *testCancelWatcher) ID() string { return tc.watcherID } func (tc *testCancelWatcher) Events() []string { return []string{tc.watcherID + ",test-event"} } func (tc *testCancelWatcher) Run(ctx context.Context, evType string) (bool, interface{}, error) { time.Sleep(10 * time.Millisecond) if tc.after == 0 { return false, nil, nil } tc.after-- return true, nil, nil } func TestCancelCallbacksAndWatchers(t *testing.T) { watcherID := "test-watcher" tests := []struct { cancelWatcherAfter int cancelSubscriberAfter int }{ {10, 20}, {20, 10}, {10, 10}, {0, 0}, {100, 200}, {200, 100}, {100, 100}, } for i, curr := range tests { t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { cancelSubscriberAfter := curr.cancelSubscriberAfter err := initWatchers([]Watcher{ &testCancelWatcher{ watcherID: watcherID, after: curr.cancelWatcherAfter, }, }) if err != nil { t.Fatalf("Failed to init/register watcher: %+v", err) } eventManager, err := New(&Config{Watchers: []string{watcherID}}) if err != nil { t.Fatalf("Failed to init event manager: %+v", err) } eventManager.Subscribe("test-watcher,test-event", nil, func(ctx context.Context, evType string, data interface{}, evData *EventData) bool { time.Sleep(1 * time.Millisecond) if cancelSubscriberAfter == 0 { return false } cancelSubscriberAfter-- return true }) eventManager.Run(context.Background()) }) } } func TestMultipleEvents(t *testing.T) { watcherID := "multiple-events" firstEvent := "multiple-events,first-event" secondEvent := "multiple-events,second-event" err := initWatchers([]Watcher{ &testMultipleEvents{ watcherID: watcherID, eventIDS: []string{firstEvent, secondEvent}, }, }) if err != nil { t.Fatalf("Failed to init/register watcher: %+v", err) } eventManager, err := New(&Config{Watchers: []string{watcherID}}) if err != nil { t.Fatalf("Failed to init event manager: %+v", err) } var hitFirstEvent bool eventManager.Subscribe(firstEvent, nil, func(ctx context.Context, evType string, data interface{}, evData *EventData) bool { hitFirstEvent = true return false }) var hitSecondEvent bool eventManager.Subscribe(secondEvent, nil, func(ctx context.Context, evType string, data interface{}, evData *EventData) bool { hitSecondEvent = true return false }) eventManager.Run(context.Background()) if !hitFirstEvent || !hitSecondEvent { t.Errorf("Failed to call back events, first event hit? (%t), second event hit? (%t)", hitFirstEvent, hitSecondEvent) } } type testMultipleEvents struct { watcherID string eventIDS []string } func (tt *testMultipleEvents) ID() string { return tt.watcherID } func (tt *testMultipleEvents) Events() []string { return tt.eventIDS } func (tt *testMultipleEvents) Run(ctx context.Context, evType string) (bool, interface{}, error) { return false, nil, nil } guest-agent-20231004.02/google_guest_agent/events/metadata/000077500000000000000000000000001450737260700233525ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/events/metadata/metadata.go000066400000000000000000000045031450737260700254630ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package metadata implement the metadata events watcher. package metadata import ( "context" "net" "net/url" "time" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) const ( // WatcherID is the metadata watcher's ID. WatcherID = "metadata-watcher" // LongpollEvent is the metadata's longpoll event type ID. LongpollEvent = "metadata-watcher,longpoll" ) var ( // arbitrarily defined wait duration(keeps behavioral backward compatibility). retryWaitDuration = 5 * time.Second ) // Watcher is the metadata event watcher implementation. type Watcher struct { client metadata.MDSClientInterface failedPrevious bool } // New allocates and initializes a new Watcher. func New() *Watcher { return &Watcher{ client: metadata.New(), } } // ID returns the metadata event watcher id. func (mp *Watcher) ID() string { return WatcherID } // Events returns an slice with all implemented events. func (mp *Watcher) Events() []string { return []string{LongpollEvent} } // Run listens to metadata changes and report back the event. func (mp *Watcher) Run(ctx context.Context, evType string) (bool, interface{}, error) { descriptor, err := mp.client.Watch(ctx) if err != nil { // Only log error once to avoid transient errors and not to spam the log on network failures. if !mp.failedPrevious { if urlErr, ok := err.(*url.Error); ok { if _, ok := urlErr.Err.(*net.OpError); ok { logger.Errorf("Network error when requesting metadata, make sure your instance has an active network and can reach the metadata server.") } } logger.Errorf("Error watching metadata: %s", err) mp.failedPrevious = true } } else { mp.failedPrevious = false } return true, descriptor, err } guest-agent-20231004.02/google_guest_agent/events/metadata/metadata_test.go000066400000000000000000000052731450737260700265270ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package metadata import ( "context" "fmt" "reflect" "testing" "github.com/GoogleCloudPlatform/guest-agent/metadata" ) var ( errUnknown = fmt.Errorf("simple error") ) type mdsClient struct { disableUnknownFailure bool } func (mds *mdsClient) Get(ctx context.Context) (*metadata.Descriptor, error) { return nil, fmt.Errorf("Get() not yet implemented") } func (mds *mdsClient) GetKey(ctx context.Context, key string, headers map[string]string) (string, error) { return "", fmt.Errorf("GetKey() not yet implemented") } func (mds *mdsClient) Watch(ctx context.Context) (*metadata.Descriptor, error) { if !mds.disableUnknownFailure { return nil, errUnknown } return nil, nil } func (mds *mdsClient) WriteGuestAttributes(ctx context.Context, key string, value string) error { return fmt.Errorf("WriteGuestattributes() not yet implemented") } func TestWatcherAPI(t *testing.T) { watcher := New() expectedEvents := []string{LongpollEvent} if !reflect.DeepEqual(watcher.Events(), expectedEvents) { t.Fatalf("watcher.Events() returned: %+v, expected: %+v.", watcher.Events(), expectedEvents) } if watcher.ID() != WatcherID { t.Errorf("watcher.ID() returned: %s, expected: %s.", watcher.ID(), WatcherID) } } func TestWatcherSuccess(t *testing.T) { watcher := New() watcher.client = &mdsClient{disableUnknownFailure: true} renew, evData, err := watcher.Run(context.Background(), LongpollEvent) if err != nil { t.Errorf("watcher.Run(%s) returned error: %+v, expected success.", LongpollEvent, err) } if !renew { t.Errorf("watcher.Run(%s) returned renew: %t, expected: true.", LongpollEvent, renew) } switch evData.(type) { case *metadata.Descriptor: default: t.Errorf("watcher.Run(%s) returned a non descriptor object.", LongpollEvent) } } func TestWatcherUnknownFailure(t *testing.T) { watcher := New() watcher.client = &mdsClient{} renew, _, err := watcher.Run(context.Background(), LongpollEvent) if err == nil { t.Errorf("watcher.Run(%s) returned no error, expected: %v.", LongpollEvent, errUnknown) } if !renew { t.Errorf("watcher.Run(%s) returned renew: %t, expected: true.", LongpollEvent, renew) } } guest-agent-20231004.02/google_guest_agent/events/sshtrustedca/000077500000000000000000000000001450737260700243065ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/events/sshtrustedca/sshtrustedca.go000066400000000000000000000040331450737260700273510ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package sshtrustedca implement the sshd trusted ca cert pipe events watcher. package sshtrustedca import ( "os" "sync" ) const ( // WatcherID is the sshtrustedca watcher's ID. WatcherID = "ssh-trusted-ca-pipe-watcher" // ReadEvent is the sshtrustedca's read event type ID. ReadEvent = "ssh-trusted-ca-pipe-watcher,read" // DefaultPipePath defines the default ssh trusted ca pipe path. DefaultPipePath = "/etc/ssh/oslogin_trustedca.pub" ) // Watcher is the sshtrustedca event watcher implementation. type Watcher struct { // pipePath points to the named pipe it's writing to. pipePath string // waitingWrite is a flag to inform the Watcher that the Handler has or // hasn't finished writing. waitingWrite bool // mutex protects waitingWrite on concurrent accesses. mutex sync.Mutex } // PipeData wraps the pipe event data. type PipeData struct { // File is the writeonly pipe's file descriptor. The user/handler must // make sure to close it after processing the event. File *os.File // Finished is a callback used by the event handler to inform the write to // the pipe is finished. Finished func() } // New allocates and initializes a new Watcher. func New(pipePath string) *Watcher { return &Watcher{ pipePath: pipePath, } } // ID returns the sshtrustedca event watcher id. func (mp *Watcher) ID() string { return WatcherID } // Events returns an slice with all implemented events. func (mp *Watcher) Events() []string { return []string{ReadEvent} } guest-agent-20231004.02/google_guest_agent/events/sshtrustedca/sshtrustedca_linux.go000066400000000000000000000073431450737260700305770ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package sshtrustedca import ( "context" "fmt" "os" "os/exec" "path/filepath" "syscall" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) // Create a named pipe if it doesn't exist. func createNamedPipe(ctx context.Context, pipePath string) error { pipeDir := filepath.Dir(pipePath) _, err := os.Stat(pipeDir) if err != nil && os.IsNotExist(err) { // The perm 0755 is compatible with distros /etc/ssh/ directory. if err := os.MkdirAll(pipeDir, 0755); err != nil { return err } } if _, err := os.Stat(pipePath); err != nil { if os.IsNotExist(err) { if err := syscall.Mkfifo(pipePath, 0644); err != nil { return fmt.Errorf("failed to create named pipe: %+v", err) } } else { return fmt.Errorf("failed to stat file: " + pipePath) } } restorecon, err := exec.LookPath("restorecon") if err != nil { logger.Infof("No restorecon available, not restoring SELinux context of: %s", pipePath) return nil } return run.Quiet(ctx, restorecon, pipePath) } // finishedCb is used by the event handler to communicate the write to the // pipe is finised, it's exposed via PipeData.Finished pointer. func (mp *Watcher) finishedCb() { mp.setWaitingWrite(false) } func (mp *Watcher) isWaitingWrite() bool { mp.mutex.Lock() defer mp.mutex.Unlock() return mp.waitingWrite } func (mp *Watcher) setWaitingWrite(val bool) { mp.mutex.Lock() defer mp.mutex.Unlock() mp.waitingWrite = val } // Run listens to ssh_trusted_ca's pipe open calls and report back the event. func (mp *Watcher) Run(ctx context.Context, evType string) (bool, interface{}, error) { var canceled bool for mp.isWaitingWrite() { time.Sleep(10 * time.Millisecond) } // Channel used to cancel the context cancelation go routine. // Used when the Watcher is returning to the event manager. cancelContext := make(chan bool) defer close(cancelContext) // Cancelation handling code. go func() { select { case <-cancelContext: break case <-ctx.Done(): canceled = true // Open the pipe as O_RDONLY to release the blocking open O_WRONLY. pipeFile, err := os.OpenFile(mp.pipePath, os.O_RDONLY, 0644) if err != nil { logger.Errorf("Failed to open readonly pipe: %+v", err) return } defer func() { if err := pipeFile.Close(); err != nil { logger.Errorf("Failed to close readonly pipe: %+v", err) } }() } }() // If the configured named pipe doesn't exists we create it before emitting events // from it. if err := createNamedPipe(ctx, mp.pipePath); err != nil { return true, nil, err } // Open the pipe as writeonly, it will block until a read is performed from the // other end of the pipe. pipeFile, err := os.OpenFile(mp.pipePath, os.O_WRONLY, 0644) if err != nil { return true, nil, err } // Have we got a ctx.Done()? if so lets just return from here and unregister // the watcher. if canceled { if err := pipeFile.Close(); err != nil { logger.Errorf("Failed to close readonly pipe: %+v", err) } return false, nil, nil } cancelContext <- true mp.setWaitingWrite(true) return true, &PipeData{File: pipeFile, Finished: mp.finishedCb}, nil } guest-agent-20231004.02/google_guest_agent/events/sshtrustedca/sshtrustedca_linux_test.go000066400000000000000000000060211450737260700316260ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package sshtrustedca import ( "context" "fmt" "io" "os" "path" "testing" "time" ) func TestPipe(t *testing.T) { // Putting a directory name between temp dir and the file name guarantees we test // the directory creation. pipePath := path.Join(t.TempDir(), "ssh", "oslogin_trustedca.pub") watcher := New(pipePath) testData := "test data transmited through the pipe." if watcher.ID() != WatcherID { t.Errorf("Wrong watcher id, expected %s, got %s", WatcherID, watcher.ID()) } timer := time.NewTimer(1 * time.Second) // This go routine simulates the reading end of the pipe, it will until the timer // is triggered (giving enough time for the Watcher to setup the pipe), when the // read operation happened the Watcher will unblock returning to the test and // the test implementation will write to the writing end of the pipe. go func() { <-timer.C readFile, err := os.OpenFile(pipePath, os.O_RDONLY, 0644) if err != nil { t.Errorf("Failed to open the read end of the pipe: %+v", err) return } defer func() { if err := readFile.Close(); err != nil { t.Errorf("Failed to close pipe(read end) file: %+v", err) } }() buff := make([]byte, 1024) var output string for { n, err := readFile.Read(buff) if err == io.EOF { break } if err != nil { t.Errorf("Failed to read pipe: %+v", err) return } if n > 0 { output = fmt.Sprintf("%s%s", output, buff[:n]) } } if output != testData { t.Errorf("Wrong data read from the pipe, expected %s, got %s", testData, output) } }() _, evData, err := watcher.Run(context.Background(), ReadEvent) if err != nil { t.Fatalf("Watcher failed: %+v", err) } pipeData := evData.(*PipeData) defer func() { if err := pipeData.File.Close(); err != nil { t.Fatalf("Failed to close pipe(write end) file: %+v", err) } pipeData.Finished() }() pipeData.File.WriteString(testData) } func TestCancel(t *testing.T) { pipePath := path.Join(t.TempDir(), "ssh", "oslogin_trustedca.pub") watcher := New(pipePath) sync := make(chan bool) defer close(sync) cancelTimer := time.NewTimer((1 * time.Second) / 2) timeoutTimer := time.NewTimer(1 * time.Second) ctx, ctxCancel := context.WithCancel(context.Background()) go func() { <-cancelTimer.C ctxCancel() sync <- true }() go func() { select { case <-timeoutTimer.C: t.Error("Watcher should have been canceled before timeout.") case <-sync: return } }() watcher.Run(ctx, ReadEvent) } guest-agent-20231004.02/google_guest_agent/events/sshtrustedca/sshtrustedca_windows.go000066400000000000000000000015521450737260700311260ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package sshtrustedca import ( "context" "fmt" ) // Run is a no-op implementation for windows. func (mp *Watcher) Run(ctx context.Context, evType string) (bool, interface{}, error) { return false, nil, fmt.Errorf("SSH Trusted CA cert pipe Watcher is not yet implemented for windows.") } guest-agent-20231004.02/google_guest_agent/fakes/000077500000000000000000000000001450737260700213575ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/fakes/fake_mds.go000066400000000000000000000101071450737260700234560ustar00rootroot00000000000000// Copyright 2023 Google LLC // // 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. // Package fakes implements fake services for unit testing. package fakes import ( "context" "fmt" "github.com/GoogleCloudPlatform/guest-agent/metadata" ) type contextKey int const ( // MDSOverride is the context key used by fake MDS for getting test conditions. MDSOverride contextKey = iota ) // MDSClient implements fake metadata server. type MDSClient struct{} // NewFakeMDSClient returns fake type MDSClient. func NewFakeMDSClient() *MDSClient { return &MDSClient{} } // GetKey implements fake GetKey MDS method. func (s MDSClient) GetKey(ctx context.Context, key string, headers map[string]string) (string, error) { valid := ` { "encrypted_credentials": "q3u9avkCiXCgKAopiG3WFKmIfwidMq+ISLEIufPDBq0EdVRt+5XnEqz1dJyNuqdeRNmP24VlsXaZ77wQtF/6qcg4t0JhUqn18VkodIUvhz8zFdYGe9peu5EprcC/h8MvSrKXS6WmWRn1920/itPo4yPKl31mOGaOwRuPYqNLVUUu1iFZZ3VZTTDp5yh3AyvLoO41UoKi6siZM+xo+PB+qoHcARGctvNfsZv+jZYbAh6PRuJ2kI4aBBp2sUFWQhAZOoDYqLpcrtTe1d9LeQC/PN/PVz5FiLOwu87YsnOGgt7/K1ce2AxDGRJaINHarricVXaqx38h0u8zei7ynTsSZIemNo9SoR6dH7feRaSiH23htHryJQMx8TV32XHzuE0GdApTLkHIqc0eZGmoJ/PGYy6INaVC+kpk+7tlZ3ZwkKneXgroyy20Iig+wfKMcj8i7ncLP01PMep9d7uFaCuoshdxJbAEeqPCNr59D7zfRBDg+QBavLKv3aPSMqFOYF1tqj2mOB1EHsasZgtDslSwDN7EhkR2YbBi2HNSNFKzEnh5SsbXINSyAgaffoK+99YrLRXCQpdaqr9GIRug6HzMzQMsXhIxr4yErVbpPcv7GSC21vi4PWU62zhvWUZ8w4HXds3HjvpJk3ILrglM72xfkddEdr1Hd7KP1F3h6nG+9FFP4s6Z6j7uHPrL+ppd7Od4dDc05hA+Unifoyshb+IaCJGtzewQtofLhyZcoEZBzp1iMT5IwSCZm6eHSwCG9hS7S9eKJAcjLBwSxWZhwO4UXU3mJM0ZTZfxUxXtmR9Ombpm5xpIu5fa4rMi1DUCKK2vrYDR5hYJrEUsFLzyK+4EGuWz+FPgMXi6gXMZZYVQCjS3zcnfBsEL18EvlDHs2muuHWE/gEjGO0nFCUFuNwkOY2bW+BU8/eKwosYxYhQk+jwYJFEuSXqtm+wgCEyFvIbg42GDc+YrKPTxAzWiBH/RL/XrPR4InDZ6extmSYZbneLjT1YRAAfLR/MOiWuY2I38Q2VYBzMqZ6y1/1EgToNMW2viYlxEVmN1ys0msospzxCGwlR0DWkSzEDJmYT2SQcKFC9OrdMZ2o6BD4s315M8lv5v7ZsL7KuoYNZ4gMBN6MrxJYD6OwdLeytCmI71LdvgVw5gdDmoChu9dFDyzPKSoMYJnvTr5ktrYwxZIyWn8Sl3BjAaslZkAwL+c5oijCTCZ+oV9vzdD7tBnFx9y3fVVFtMC3nflyEjInEUPCupxh38O4TsYLLVl7tttL696kUKdlHL1SRAFCX1Wb5p4WNSBzQQtTGU1dsw904CncAj32sW32oGFWqb4Bom1OzoV/equ32Anef8J95mF+ahmf1BvTUMUq5Az2mSi2/dFBhuhy7rhGQyVWpwCEzpzVpVlysDr5aWr8CLbDOLzJv3MIDM3QQ=", "key_import_blob": { "duplicate": "ACAFYwCs8qzuSCCTvS1iCIHVTDuEXrP7WNNYPGl44ZPARLbhYVWaSkttYk1J2ChEEwG+u0fRxBVF95nEbe3xzN17+pppFFKelB9Jlf+PybtE0rRMyIJ0CB4HT9w=", "encrypted_seed": "ACBnqcxLycU+VUxeB89a7DCa0BSqOciydCReXia87EDLjQAgEUyXgTSjqA4tOxRNARnW5fw4B2p6AJFLD1nZx+llJP8=", "public_area": "AAgACwAAAEAAAAAQACCmhjk4ZFa6nbv58ya74lshnfNfGaCta6+hPIR5s+hZBw==" } } ` invalid := ` { "encrypted_credentials": "q3u9avkCLOwu87YsnOmNo9SoR6d/dFBhuhy7rhGQyVWpwCEzpzVpVlysDr5aWr8CLbDOLzJv3MIDM3QQ=", "key_import_blob": { "duplicate": "ACAFYwCs8qzuSCCTvS1iCIHVT9Jlf+PybtE0rRMyIJ0CB4HT9w=", "encrypted_seed": "ACBnqcxLycU+VUxeB8D1nZx+llJP8=", "public_area": "AAgACwAAAEAAAA+hPIR5s+hZBw==" } } ` switch ctx.Value(MDSOverride) { case "succeed": return valid, nil case "fail_mds_connect": return "", fmt.Errorf("this is fake MDS error") case "fail_unmarshal": return invalid, nil default: return "", nil } } // Get method implements fake Get on MDS. func (s MDSClient) Get(context.Context) (*metadata.Descriptor, error) { return nil, fmt.Errorf("not yet implemented") } // Watch method implements fake watcher on MDS. func (s MDSClient) Watch(context.Context) (*metadata.Descriptor, error) { return nil, fmt.Errorf("not yet implemented") } // WriteGuestAttributes method implements fake writer on MDS. func (s MDSClient) WriteGuestAttributes(context.Context, string, string) error { return fmt.Errorf("not yet implemented") } guest-agent-20231004.02/google_guest_agent/instance_setup.go000066400000000000000000000244571450737260700236550ustar00rootroot00000000000000// Copyright 2019 Google LLC // 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 // https://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. package main import ( "context" "fmt" "net" "os" "os/exec" "runtime" "sort" "strings" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/go-ini/ini" ) func getDefaultAdapter(fes []ipForwardEntry) (*ipForwardEntry, error) { // Choose the first adapter index that has the default route setup. // This is equivalent to how route.exe works when interface is not provided. sort.Slice(fes, func(i, j int) bool { return fes[i].ipForwardIfIndex < fes[j].ipForwardIfIndex }) for _, fe := range fes { if fe.ipForwardDest.Equal(net.ParseIP("0.0.0.0")) { return &fe, nil } } return nil, fmt.Errorf("could not find default route") } func addMetadataRoute() error { fes, err := getIPForwardEntries() if err != nil { return err } defaultRoute, err := getDefaultAdapter(fes) if err != nil { return err } forwardEntry := ipForwardEntry{ ipForwardDest: net.ParseIP("169.254.169.254"), ipForwardMask: net.IPv4Mask(255, 255, 255, 255), ipForwardNextHop: net.ParseIP("0.0.0.0"), ipForwardMetric1: defaultRoute.ipForwardMetric1, // Must be <= the default route metric. ipForwardIfIndex: defaultRoute.ipForwardIfIndex, } for _, fe := range fes { if fe.ipForwardDest.Equal(forwardEntry.ipForwardDest) && fe.ipForwardIfIndex == forwardEntry.ipForwardIfIndex { // No need to add entry, it's already setup. return nil } } logger.Infof("Adding route to metadata server on adapter with index %d", defaultRoute.ipForwardIfIndex) return addIPForwardEntry(forwardEntry) } func agentInit(ctx context.Context) { // Actions to take on agent startup. // // All platforms: // - Determine if metadata hostname can be resolved. // // On Windows: // - Add route to metadata server // On Linux: // - Generate SSH host keys (one time only). // - Generate boto.cfg (one time only). // - Set sysctl values. // - Set scheduler values. // - Run `google_optimize_local_ssd` script. // - Run `google_set_multiqueue` script. // TODO incorporate these scripts into the agent. liamh@12-11-19 config := cfg.Get() if runtime.GOOS == "windows" { // Indefinitely retry to set up required MDS route. for ; ; time.Sleep(1 * time.Second) { if err := addMetadataRoute(); err != nil { logger.Errorf("Could not set default route to metadata: %v", err) } else { break } } } else { // Linux instance setup. defer run.Quiet(ctx, "systemd-notify", "--ready") defer logger.Debugf("notify systemd") if config.Snapshots.Enabled { logger.Infof("Snapshot listener enabled") snapshotServiceIP := config.Snapshots.SnapshotServiceIP snapshotServicePort := config.Snapshots.SnapshotServicePort timeoutInSeconds := config.Snapshots.TimeoutInSeconds startSnapshotListener(ctx, snapshotServiceIP, snapshotServicePort, timeoutInSeconds) } scripts := []struct { enabled bool script string }{ {config.InstanceSetup.OptimizeLocalSSD, "optimize_local_ssd"}, {config.InstanceSetup.SetMultiqueue, "set_multiqueue"}, } // These scripts are run regardless of metadata/network access and config options. for _, curr := range scripts { if !curr.enabled { continue } if err := run.Quiet(ctx, "google_"+curr.script); err != nil { logger.Warningf("Failed to run %q script: %v", "google_"+curr.script, err) } } // Below actions happen on every agent start. They only need to // run once per boot, but it's harmless to run them on every // boot. If this changes, we will hook these to an explicit // on-boot signal. logger.Debugf("set IO scheduler config") if err := setIOScheduler(); err != nil { logger.Warningf("Failed to set IO scheduler: %v", err) } // Allow users to opt out of below instance setup actions. if !config.InstanceSetup.NetworkEnabled { logger.Infof("InstanceSetup.network_enabled is false, skipping setup actions that require metadata") return } // The below actions require metadata to be set, so if it // hasn't yet been set, wait on it here. In instances without // network access, this will become an indefinite wait. // TODO: split agentInit into needs-network and no-network functions. for newMetadata == nil { logger.Debugf("populate first time metadata...") newMetadata, _ = mdsClient.Get(ctx) } // Disable overcommit accounting; e2 instances only. parts := strings.Split(newMetadata.Instance.MachineType, "/") if strings.HasPrefix(parts[len(parts)-1], "e2-") { if err := run.Quiet(ctx, "sysctl", "vm.overcommit_memory=1"); err != nil { logger.Warningf("Failed to run 'sysctl vm.overcommit_memory=1': %v", err) } } // Check if instance ID has changed, and if so, consider this // the first boot of the instance. // TODO Also do this for windows. liamh@13-11-2019 instanceIDFile := config.Instance.InstanceIDDir instanceID, err := os.ReadFile(instanceIDFile) if err != nil && !os.IsNotExist(err) { logger.Warningf("Not running first-boot actions, error reading instance ID: %v", err) } else { if string(instanceID) == "" { // If the file didn't exist or was empty, try legacy key from instance configs. instanceID = []byte(config.Instance.InstanceID) // Write instance ID to file for next time before moving on. towrite := fmt.Sprintf("%s\n", newMetadata.Instance.ID.String()) if err := os.WriteFile(instanceIDFile, []byte(towrite), 0644); err != nil { logger.Warningf("Failed to write instance ID file: %v", err) } } if newMetadata.Instance.ID.String() != strings.TrimSpace(string(instanceID)) { logger.Infof("Instance ID changed, running first-boot actions") if config.InstanceSetup.SetHostKeys { if err := generateSSHKeys(ctx); err != nil { logger.Warningf("Failed to generate SSH keys: %v", err) } } if config.InstanceSetup.SetBotoConfig { if err := generateBotoConfig(); err != nil { logger.Warningf("Failed to create boto.cfg: %v", err) } } // Write instance ID to file. towrite := fmt.Sprintf("%s\n", newMetadata.Instance.ID.String()) if err := os.WriteFile(instanceIDFile, []byte(towrite), 0644); err != nil { logger.Warningf("Failed to write instance ID file: %v", err) } } } } } func generateSSHKeys(ctx context.Context) error { config := cfg.Get() hostKeyDir := config.InstanceSetup.HostKeyDir dir, err := os.Open(hostKeyDir) if err != nil { return err } defer dir.Close() files, err := dir.Readdirnames(0) if err != nil { return err } keytypes := make(map[string]bool) // Find keys present on disk, and deduce their type from filename. prefix := "ssh_host_" suffix := "_key" for _, file := range files { if strings.HasPrefix(file, prefix) && strings.HasSuffix(file, suffix) && len(file) > len(prefix+suffix) { keytype := file keytype = strings.TrimPrefix(keytype, prefix) keytype = strings.TrimSuffix(keytype, suffix) keytypes[keytype] = true } } // List keys we should generate, according to the config. configKeys := config.InstanceSetup.HostKeyTypes for _, keytype := range strings.Split(configKeys, ",") { keytypes[keytype] = true } // Generate new keys and upload to guest attributes. for keytype := range keytypes { keyfile := fmt.Sprintf("%s/ssh_host_%s_key", hostKeyDir, keytype) if err := run.Quiet(ctx, "ssh-keygen", "-t", keytype, "-f", keyfile+".temp", "-N", "", "-q"); err != nil { logger.Warningf("Failed to generate SSH host key %q: %v", keyfile, err) continue } if err := os.Chmod(keyfile+".temp", 0600); err != nil { logger.Errorf("Failed to chmod SSH host key %q: %v", keyfile, err) continue } if err := os.Chmod(keyfile+".temp.pub", 0644); err != nil { logger.Errorf("Failed to chmod SSH host key %q: %v", keyfile+".pub", err) continue } if err := os.Rename(keyfile+".temp", keyfile); err != nil { logger.Errorf("Failed to overwrite %q: %v", keyfile, err) continue } if err := os.Rename(keyfile+".temp.pub", keyfile+".pub"); err != nil { logger.Errorf("Failed to overwrite %q: %v", keyfile+".pub", err) continue } pubKey, err := os.ReadFile(keyfile + ".pub") if err != nil { logger.Errorf("Can't read %s public key: %v", keytype, err) continue } if vals := strings.Split(string(pubKey), " "); len(vals) >= 2 { if err := mdsClient.WriteGuestAttributes(ctx, "hostkeys/"+vals[0], vals[1]); err != nil { logger.Errorf("Failed to upload %s key to guest attributes: %v", keytype, err) } } else { logger.Warningf("Generated key is malformed, not uploading") } } _, err = exec.LookPath("restorecon") if err == nil { if err := run.Quiet(ctx, "restorecon", "-FR", hostKeyDir); err != nil { return fmt.Errorf("failed to restore SELinux context for: %s", hostKeyDir) } } return nil } func generateBotoConfig() error { path := "/etc/boto.cfg" botoCfg, err := ini.LooseLoad(path, path+".template") if err != nil { return err } botoCfg.Section("GSUtil").Key("default_project_id").SetValue(newMetadata.Project.NumericProjectID.String()) botoCfg.Section("GSUtil").Key("default_api_version").SetValue("2") botoCfg.Section("GoogleCompute").Key("service_account").SetValue("default") return botoCfg.SaveTo(path) } func setIOScheduler() error { dir, err := os.Open("/sys/block") if err != nil { return err } defer dir.Close() devs, err := dir.Readdirnames(0) if err != nil { return err } for _, dev := range devs { // Detect if device is using MQ subsystem. stat, err := os.Stat("/sys/block/" + dev + "/mq") if err == nil && stat.IsDir() { f, err := os.OpenFile("/sys/block/"+dev+"/queue/scheduler", os.O_WRONLY|os.O_TRUNC, 0700) if err != nil { return err } _, err = f.Write([]byte("none")) if err != nil { return err } } } return nil } guest-agent-20231004.02/google_guest_agent/instance_setup_integ_test.go000066400000000000000000000123521450737260700260710ustar00rootroot00000000000000// Copyright 2021 Google LLC // // 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. //go:build integration // +build integration package main import ( "context" "os" "path/filepath" "strings" "testing" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" ) const ( botoCfg = "/etc/boto.cfg" ) func getConfig(t *testing.T) (*cfg.Sections, string) { t.Helper() if err := cfg.Load(nil); err != nil { t.Fatalf("Failed to load configuration: %+v", err) } config, err := cfg.Get() if err != nil { t.Fatalf("Failed to get config: %+v", err) } if config == nil { t.Fatal("cfg.Get() returned a nil config") } tempDir := filepath.Join(t.TempDir(), "test_instance_setup") err := os.Mkdir(tempDir, 0755) if err != nil { t.Fatalf("Failed to create working dir: %+v", err) } // Configure a non-standard instance ID dir for us to play with. config.Instance.InstanceIDDir = tempDir config.InstanceSetup.HostKeyDir = tempDir return config, tempDir } // TestInstanceSetupSSHKeys validates SSH keys are generated on first boot and not changed afterward. func TestInstanceSetupSSHKeys(t *testing.T) { config, tempDir := getConfig(t) ctx := context.Background() agentInit(ctx) if _, err := os.Stat(tempDir + "/google_instance_id"); err != nil { t.Fatal("instance ID File was not created by agentInit") } dir, err := os.Open(tempDir) if err != nil { t.Fatal("failed to open working dir") } defer dir.Close() files, err := dir.Readdirnames(0) if err != nil { t.Fatal("failed to read files") } var keys []string for _, file := range files { if strings.HasPrefix(file, "ssh_host_") { keys = append(keys, file) } } if len(keys) == 0 { t.Fatal("instance setup didn't create SSH host keys") } // Remove one key file and run again to confirm SSH keys have not // changed because the instance ID file has not changed. if err := os.Remove(tempDir + "/" + keys[0]); err != nil { t.Fatal("failed to remove key file") } agentInit(ctx) if _, err := dir.Seek(0, 0); err != nil { t.Fatal("failed to rewind dir for second check") } files2, err := dir.Readdirnames(0) if err != nil { t.Fatal("failed to read files") } var keys2 []string for _, file := range files2 { if strings.HasPrefix(file, "ssh_host_") { keys2 = append(keys2, file) } if file == keys[0] { t.Fatalf("agentInit recreated key %s", file) } } if len(keys) == len(keys2) { t.Fatal("agentInit recreated SSH host keys") } } // TestInstanceSetupSSHKeysDisabled validates the config option to disable host // key generation is respected. func TestInstanceSetupSSHKeysDisabled(t *testing.T) { config, tempDir := getConfig(t) // Disable SSH host key generation. config.InstanceSetup.SetHostKeys = false ctx := context.Background() agentInit(ctx) dir, err := os.Open(tempDir) if err != nil { t.Fatal("failed to open working dir") } defer dir.Close() files, err := dir.Readdirnames(0) if err != nil { t.Fatal("failed to read files") } for _, file := range files { if strings.HasPrefix(file, "ssh_host_") { t.Fatal("agentInit created SSH host keys when disabled") } } } func TestInstanceSetupBotoConfig(t *testing.T) { config, tempDir := getConfig(t) ctx := context.Background() if err := os.Rename(botoCfg, botoCfg+".bak"); err != nil { t.Fatalf("failed to move boto config: %v", err) } defer func() { // Restore file at end of test. if err := os.Rename(botoCfg+".bak", botoCfg); err != nil { t.Fatalf("failed to restore boto config: %v", err) } }() // Test it is created by default on first boot agentInit(ctx) if _, err := os.Stat(botoCfg); err != nil { t.Fatal("boto config was not created on first boot") } // Test it is not recreated on subsequent invocations if err := os.Remove(botoCfg); err != nil { t.Fatal("failed to remove boto config") } agentInit(ctx) if _, err := os.Stat(botoCfg); err == nil || !os.IsNotExist(err) { // If we didn't get an error, or if we got some other kind of error t.Fatal("boto config was recreated after first boot") } } func TestInstanceSetupBotoConfigDisabled(t *testing.T) { config, _ := getConfig(t, tempDir) ctx := context.Background() if err := os.Rename(botoCfg, botoCfg+".bak"); err != nil { t.Fatalf("failed to move boto config: %v", err) } defer func() { // Restore file at end of test. if err := os.Rename(botoCfg+".bak", botoCfg); err != nil { t.Fatalf("failed to restore boto config: %v", err) } }() // Test it is not created if disabled in config. config.Section("InstanceSetup").Key("set_boto_config").SetValue("false") agentInit(ctx) if _, err := os.Stat(botoCfg); err == nil || !os.IsNotExist(err) { // If we didn't get an error, or if we got some other kind of error t.Fatal("boto config was created when disabled in config") } } guest-agent-20231004.02/google_guest_agent/main.go000066400000000000000000000175151450737260700215520ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. // GCEGuestAgent is the Google Compute Engine guest agent executable. package main import ( "context" "fmt" "io" "os" "runtime" "strings" "sync" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/agentcrypto" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events" mdsEvent "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/metadata" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/sshtrustedca" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/osinfo" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/scheduler" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/sshca" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/telemetry" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) // Certificates wrapps a list of certificate authorities. type Certificates struct { Certs []TrustedCert `json:"trustedCertificateAuthorities"` } // TrustedCert defines the object containing a public key. type TrustedCert struct { PublicKey string `json:"publicKey"` } var ( programName = "GCEGuestAgent" version string oldMetadata, newMetadata *metadata.Descriptor osInfo osinfo.OSInfo mdsClient *metadata.Client ) const ( regKeyBase = `SOFTWARE\Google\ComputeEngine` ) type manager interface { Diff(ctx context.Context) (bool, error) Disabled(ctx context.Context) (bool, error) Set(ctx context.Context) error Timeout(ctx context.Context) (bool, error) } func logStatus(name string, disabled bool) { var status string switch disabled { case false: status = "enabled" case true: status = "disabled" } logger.Infof("GCE %s manager status: %s", name, status) } func closeFile(c io.Closer) { err := c.Close() if err != nil { logger.Warningf("Error closing file: %v.", err) } } func availableManagers() []manager { managers := []manager{ &addressMgr{}, } if runtime.GOOS == "windows" { return append(managers, newWsfcManager(), &winAccountsMgr{}, &diagnosticsMgr{}, ) } return append(managers, &clockskewMgr{}, &osloginMgr{}, &accountsMgr{}, ) } func runUpdate(ctx context.Context) { var wg sync.WaitGroup for _, mgr := range availableManagers() { wg.Add(1) go func(mgr manager) { defer wg.Done() disabled, err := mgr.Disabled(ctx) if err != nil { logger.Errorf("Failed to run manager's Disabled() call: %+v", err) return } if disabled { logger.Debugf("manager %#v disabled, skipping", mgr) return } timeout, err := mgr.Timeout(ctx) if err != nil { logger.Errorf("[%#v] Failed to run manager Timeout() call: %+v", mgr, err) return } diff, err := mgr.Diff(ctx) if err != nil { logger.Errorf("[%#v] Failed to run manager Diff() call: %+v", mgr, err) return } if !timeout && !diff { logger.Debugf("[%#v] Manager reports no diff", mgr) return } logger.Debugf("running %#v manager", mgr) if err := mgr.Set(ctx); err != nil { logger.Errorf("[%#v] Failed to run manager Set() call: %s", mgr, err) } }(mgr) } wg.Wait() } func runAgent(ctx context.Context) { opts := logger.LogOpts{LoggerName: programName} if runtime.GOOS == "windows" { opts.FormatFunction = logFormatWindows opts.Writers = []io.Writer{&utils.SerialPort{Port: "COM1"}} } else { opts.FormatFunction = logFormat opts.Writers = []io.Writer{os.Stdout} // Local logging is syslog; we will just use stdout in Linux. opts.DisableLocalLogging = true } if os.Getenv("GUEST_AGENT_DEBUG") != "" { opts.Debug = true } if err := logger.Init(ctx, opts); err != nil { fmt.Printf("Error initializing logger: %v", err) os.Exit(1) } logger.Infof("GCE Agent Started (version %s)", version) osInfo = osinfo.Get() mdsClient = metadata.New() agentInit(ctx) // Previous request to metadata *may* not have worked becasue routes don't get added until agentInit. var err error if newMetadata == nil { /// Error here doesn't matter, if we cant get metadata, we cant record telemetry. newMetadata, err = mdsClient.Get(ctx) if err != nil { logger.Debugf("Error getting metdata: %v", err) } } // Try to re-initialize logger now, we know after agentInit() is more likely to have metadata available. // TODO: move all this metadata dependent code to its own metadata event handler. if newMetadata != nil { opts.ProjectName = newMetadata.Project.ProjectID if err := logger.Init(ctx, opts); err != nil { logger.Errorf("Error initializing logger: %v", err) } } // knownJobs is list of default jobs that run on a pre-defined schedule. knownJobs := []scheduler.Job{telemetry.New(mdsClient, programName, version)} scheduler.ScheduleJobs(ctx, knownJobs, false) // Schedules jobs that need to be started before notifying systemd Agent process has started. if cfg.Get().Unstable.MDSMTLS { scheduler.ScheduleJobs(ctx, []scheduler.Job{agentcrypto.New()}, true) } eventsConfig := &events.Config{ Watchers: []string{ mdsEvent.WatcherID, sshtrustedca.WatcherID, }, } eventManager, err := events.New(eventsConfig) if err != nil { logger.Errorf("Error initializing event manager: %v", err) return } sshca.Init(eventManager) oldMetadata = &metadata.Descriptor{} eventManager.Subscribe(mdsEvent.LongpollEvent, nil, func(ctx context.Context, evType string, data interface{}, evData *events.EventData) bool { logger.Debugf("Handling metadata %q event.", evType) // If metadata watcher failed there isn't much we can do, just ignore the event and // allow the water to get it corrected. if evData.Error != nil { logger.Infof("Metadata event watcher failed, ignoring: %+v", evData.Error) return true } if evData.Data == nil { logger.Infof("Metadata event watcher didn't pass in the metadata, ignoring.") return true } newMetadata = evData.Data.(*metadata.Descriptor) runUpdate(ctx) oldMetadata = newMetadata return true }) eventManager.Run(ctx) logger.Infof("GCE Agent Stopped") } func logFormatWindows(e logger.LogEntry) string { now := time.Now().Format("2006/01/02 15:04:05") // 2006/01/02 15:04:05 GCEGuestAgent This is a log message. return fmt.Sprintf("%s %s: %s", now, programName, e.Message) } func logFormat(e logger.LogEntry) string { switch e.Severity { case logger.Error, logger.Critical, logger.Debug: // ERROR file.go:82 This is a log message. return fmt.Sprintf("%s %s:%d %s", strings.ToUpper(e.Severity.String()), e.Source.File, e.Source.Line, e.Message) default: // This is a log message. return e.Message } } func closer(c io.Closer) { err := c.Close() if err != nil { logger.Warningf("Error closing %v: %v.", c, err) } } func main() { ctx := context.Background() if err := cfg.Load(nil); err != nil { fmt.Fprintf(os.Stderr, "Failed to load configuration: %+v", err) os.Exit(1) } var action string if len(os.Args) < 2 { action = "run" } else { action = os.Args[1] } if action == "noservice" { runAgent(ctx) os.Exit(0) } if err := register(ctx, "GCEAgent", "GCEAgent", "", runAgent, action); err != nil { logger.Fatalf("error registering service: %s", err) } } guest-agent-20231004.02/google_guest_agent/non_windows_accounts.go000066400000000000000000000336101450737260700250630ustar00rootroot00000000000000// Copyright 2019 Google LLC // 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 // https://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. package main import ( "bufio" "bytes" "context" "fmt" "os" "os/exec" "path" "runtime" "sort" "strconv" "strings" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) var ( // sshKeys is a cache of what we have added to each managed users' authorized // keys file. Avoids necessity of re-reading all files on every change. sshKeys map[string][]string googleUsersFile = "/var/lib/google/google_users" ) // compareStringSlice returns true if two string slices are equal, false // otherwise. Does not modify the slices. func compareStringSlice(first, second []string) bool { if len(first) != len(second) { return false } for _, list := range [][]string{first, second} { sortfunc := func(i, j int) bool { return list[i] < list[j] } list = append([]string{}, list...) sort.Slice(list, sortfunc) } for idx := range first { if first[idx] != second[idx] { return false } } return true } func removeExpiredKeys(keys []string) []string { var validKeys []string for _, key := range keys { if err := utils.CheckExpiredKey(key); err == nil { validKeys = append(validKeys, key) } } return validKeys } type accountsMgr struct{} func (a *accountsMgr) Diff(ctx context.Context) (bool, error) { // If any keys have changed. if !compareStringSlice(newMetadata.Instance.Attributes.SSHKeys, oldMetadata.Instance.Attributes.SSHKeys) { return true, nil } if !compareStringSlice(newMetadata.Project.Attributes.SSHKeys, oldMetadata.Project.Attributes.SSHKeys) { return true, nil } if newMetadata.Instance.Attributes.BlockProjectKeys != oldMetadata.Instance.Attributes.BlockProjectKeys { return true, nil } // If any on-disk keys have expired. for _, keys := range sshKeys { if len(keys) != len(removeExpiredKeys(keys)) { return true, nil } } // If we've just disabled OS Login. oldOslogin, _, _ := getOSLoginEnabled(oldMetadata) newOslogin, _, _ := getOSLoginEnabled(newMetadata) if oldOslogin && !newOslogin { return true, nil } return false, nil } func (a *accountsMgr) Timeout(ctx context.Context) (bool, error) { return false, nil } func (a *accountsMgr) Disabled(ctx context.Context) (bool, error) { config := cfg.Get() oslogin, _, _ := getOSLoginEnabled(newMetadata) return false || runtime.GOOS == "windows" || oslogin || !config.Daemons.AccountsDaemon, nil } func (a *accountsMgr) Set(ctx context.Context) error { config := cfg.Get() if sshKeys == nil { logger.Debugf("initialize sshKeys map") sshKeys = make(map[string][]string) } logger.Debugf("create sudoers file if needed") if err := createSudoersFile(); err != nil { logger.Errorf("Error creating google-sudoers file: %v.", err) } logger.Debugf("create sudoers group if needed") if err := createSudoersGroup(ctx, config); err != nil { logger.Errorf("Error creating google-sudoers group: %v.", err) } mdkeys := newMetadata.Instance.Attributes.SSHKeys if !newMetadata.Instance.Attributes.BlockProjectKeys { mdkeys = append(mdkeys, newMetadata.Project.Attributes.SSHKeys...) } mdKeyMap := getUserKeys(mdkeys) logger.Debugf("read google users file") gUsers, err := readGoogleUsersFile() if err != nil { // TODO: is this OK to continue past? logger.Errorf("Couldn't read google_users file: %v.", err) } // Update SSH keys, creating Google users as needed. for user, userKeys := range mdKeyMap { if _, err := getPasswd(user); err != nil { logger.Infof("Creating user %s.", user) if err := createGoogleUser(ctx, config, user); err != nil { logger.Errorf("Error creating user: %s.", err) continue } gUsers[user] = "" } if _, ok := gUsers[user]; !ok { logger.Infof("Adding existing user %s to google-sudoers group.", user) if err := addUserToGroup(ctx, user, "google-sudoers"); err != nil { logger.Errorf("%v.", err) } } if !compareStringSlice(userKeys, sshKeys[user]) { logger.Infof("Updating keys for user %s.", user) if err := updateAuthorizedKeysFile(ctx, user, userKeys); err != nil { logger.Errorf("Error updating SSH keys for %s: %v.", user, err) continue } sshKeys[user] = userKeys } } // Remove Google users not found in metadata. for user := range gUsers { if _, ok := mdKeyMap[user]; !ok && user != "" { logger.Infof("Removing user %s.", user) err = removeGoogleUser(ctx, config, user) if err != nil { logger.Errorf("Error removing user: %v.", err) } delete(sshKeys, user) } } // Update the google_users file if we've added or removed any users. logger.Debugf("write google_users file") if err := writeGoogleUsersFile(); err != nil { logger.Errorf("Error writing google_users file: %v.", err) } // Start SSHD if not started. We do this in agent instead of adding a // Wants= directive, and here instead of instance setup, so that this // can be disabled by the instance configs file. for _, svc := range []string{"ssh", "sshd"} { // Ignore output, it's just a best effort. systemctlStart(ctx, svc) } return nil } var badSSHKeys []string // getUserKeys returns the keys which are not expired and non-expiring key. // valid formats are: // user:ssh-rsa [KEY_VALUE] [USERNAME] // user:ssh-rsa [KEY_VALUE] // user:ssh-rsa [KEY_VALUE] google-ssh {"userName":"[USERNAME]","expireOn":"[EXPIRE_TIME]"} func getUserKeys(mdkeys []string) map[string][]string { mdKeyMap := make(map[string][]string) for i := 0; i < len(mdkeys); i++ { trimmedKey := strings.Trim(mdkeys[i], " ") if trimmedKey != "" { user, keyVal, err := utils.GetUserKey(trimmedKey) if err == nil { err = utils.ValidateUserKey(user, keyVal) } if err != nil { if !utils.ContainsString(trimmedKey, badSSHKeys) { logger.Errorf("%s: %s", err.Error(), trimmedKey) badSSHKeys = append(badSSHKeys, trimmedKey) } continue } // key which is not expired or non-expiring key, add it. userKeys := mdKeyMap[user] userKeys = append(userKeys, keyVal) mdKeyMap[user] = userKeys } } return mdKeyMap } // passwdEntry is a user.User with omitted passwd fields restored. type passwdEntry struct { Username string Passwd string UID int GID int Name string HomeDir string Shell string } // getPasswd returns a passwdEntry from the local passwd database. Code adapted from os/user func getPasswd(user string) (*passwdEntry, error) { prefix := []byte(user + ":") colon := []byte{':'} parse := func(line []byte) (*passwdEntry, error) { if !bytes.HasPrefix(line, prefix) || bytes.Count(line, colon) < 6 { return nil, nil } // kevin:x:1005:1006::/home/kevin:/usr/bin/zsh parts := strings.SplitN(string(line), ":", 7) if len(parts) < 7 { return nil, fmt.Errorf("invalid passwd entry for %s", user) } uid, err := strconv.Atoi(parts[2]) if err != nil { return nil, fmt.Errorf("invalid passwd entry for %s", user) } gid, err := strconv.Atoi(parts[3]) if err != nil { return nil, fmt.Errorf("invalid passwd entry for %s", user) } u := &passwdEntry{ Username: parts[0], Passwd: parts[1], UID: uid, GID: gid, Name: parts[4], HomeDir: parts[5], Shell: parts[6], } return u, nil } passwd, err := os.Open("/etc/passwd") if err != nil { return nil, err } bs := bufio.NewScanner(passwd) for bs.Scan() { line := bs.Bytes() // There's no spec for /etc/passwd or /etc/group, but we try to follow // the same rules as the glibc parser, which allows comments and blank // space at the beginning of a line. line = bytes.TrimSpace(line) if len(line) == 0 || line[0] == '#' { continue } v, err := parse(line) if v != nil || err != nil { return v, err } } return nil, fmt.Errorf("user not found") } func writeGoogleUsersFile() error { dir := path.Dir(googleUsersFile) if _, err := os.Stat(dir); err != nil { if err = os.Mkdir(dir, 0755); err != nil { return err } } gfile, err := os.OpenFile(googleUsersFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err == nil { defer gfile.Close() for user := range sshKeys { fmt.Fprintf(gfile, "%s\n", user) } } return err } func readGoogleUsersFile() (map[string]string, error) { res := make(map[string]string) gUsers, err := os.ReadFile(googleUsersFile) if err != nil && !os.IsNotExist(err) { return nil, err } for _, user := range strings.Split(string(gUsers), "\n") { if user != "" { res[user] = "" } } return res, nil } // Replaces {user} or {group} in command string. Supports legacy python-era // user command overrides. func createUserGroupCmd(cmd, user, group string) (string, []string) { cmd = strings.Replace(cmd, "{user}", user, 1) cmd = strings.Replace(cmd, "{group}", group, 1) // We don't run the command here because we might need the exit codes. tokens := strings.Fields(cmd) return tokens[0], tokens[1:] } // createGoogleUser creates a Google managed user account if needed and adds it // to the configured groups. func createGoogleUser(ctx context.Context, config *cfg.Sections, user string) error { var uid string if config.Accounts.ReuseHomedir { uid = getUID(fmt.Sprintf("/home/%s", user)) } if err := createUser(ctx, user, uid); err != nil { return err } groups := config.Accounts.Groups for _, group := range strings.Split(groups, ",") { addUserToGroup(ctx, user, group) } return addUserToGroup(ctx, user, "google-sudoers") } // removeGoogleUser removes Google managed users. If deprovision_remove is true, the // user and its home directory are removed. Otherwise, SSH keys and sudoer // permissions are removed but the user remains on the system. Group membership // is not changed. func removeGoogleUser(ctx context.Context, config *cfg.Sections, user string) error { if config.Accounts.DeprovisionRemove { userdel := config.Accounts.UserDelCmd name, args := createUserGroupCmd(userdel, user, "") return run.Quiet(ctx, name, args...) } if err := updateAuthorizedKeysFile(ctx, user, []string{}); err != nil { return err } gpasswddel := config.Accounts.GPasswdRemoveCmd name, args := createUserGroupCmd(gpasswddel, user, "google-sudoers") return run.Quiet(ctx, name, args...) } // createSudoersFile creates the google_sudoers configuration file if it does // not exist and specifies the group 'google-sudoers' should have all // permissions. func createSudoersFile() error { sudoFile, err := os.OpenFile("/etc/sudoers.d/google_sudoers", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0440) if err != nil { if os.IsExist(err) { return nil } return err } defer sudoFile.Close() fmt.Fprintf(sudoFile, "%%google-sudoers ALL=(ALL:ALL) NOPASSWD:ALL\n") return nil } // createSudoersGroup creates the google-sudoers group if it does not exist. func createSudoersGroup(ctx context.Context, config *cfg.Sections) error { groupadd := config.Accounts.GroupAddCmd name, args := createUserGroupCmd(groupadd, "", "google-sudoers") ret := run.WithOutput(ctx, name, args...) if ret.ExitCode == 9 { // 9 means group already exists. return nil } if ret.ExitCode != 0 { return error(ret) } logger.Infof("Created google sudoers file") return nil } // updateAuthorizedKeysFile adds provided keys to the user's SSH // AuthorizedKeys file. The file and containing directory are created if it // does not exist. Uses a temporary file to avoid partial updates in case of // errors. If no keys are provided, the authorized keys file is removed. func updateAuthorizedKeysFile(ctx context.Context, user string, keys []string) error { gcomment := "# Added by Google" passwd, err := getPasswd(user) if err != nil { return err } if passwd.HomeDir == "" { return fmt.Errorf("user %s has no homedir set", user) } if passwd.Shell == "/sbin/nologin" { return nil } sshpath := path.Join(passwd.HomeDir, ".ssh") if _, err := os.Stat(sshpath); err != nil { if os.IsNotExist(err) { if err = os.Mkdir(sshpath, 0700); err != nil { return err } if err = os.Chown(sshpath, passwd.UID, passwd.GID); err != nil { return err } } else { return err } } akpath := path.Join(sshpath, "authorized_keys") // Remove empty file. if len(keys) == 0 { os.Remove(akpath) return nil } tempPath := akpath + ".google" akcontents, err := os.ReadFile(akpath) if err != nil && !os.IsNotExist(err) { return err } var isgoogle bool var userKeys []string for _, key := range strings.Split(string(akcontents), "\n") { if key == "" { continue } if isgoogle { isgoogle = false continue } if key == gcomment { isgoogle = true continue } userKeys = append(userKeys, key) } newfile, err := os.OpenFile(tempPath, os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return err } defer newfile.Close() for _, key := range userKeys { fmt.Fprintf(newfile, "%s\n", key) } for _, key := range keys { fmt.Fprintf(newfile, "%s\n%s\n", gcomment, key) } err = os.Chown(tempPath, passwd.UID, passwd.GID) if err != nil { // Existence of temp file will block further updates for this user. // Don't catch remove error, nothing we can do. Return the // chown error which caused the issue. os.Remove(tempPath) return fmt.Errorf("error setting ownership of new keys file: %v", err) } _, err = exec.LookPath("restorecon") if err == nil { if err := run.Quiet(ctx, "restorecon", tempPath); err != nil { return fmt.Errorf("error setting selinux context: %+v", err) } } return os.Rename(tempPath, akpath) } guest-agent-20231004.02/google_guest_agent/non_windows_accounts_integ_test.go000066400000000000000000000060011450737260700273020ustar00rootroot00000000000000// Copyright 2021 Google LLC // // 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. //go:build integration // +build integration package main import ( "fmt" "os" "os/exec" "strings" "testing" ) const ( testUser = "integration-test-user" defaultgroupstring = "adm,dip,docker,lxd,plugdev,video,google-sudoers" ) func TestCreateAndRemoveGoogleUser(t *testing.T) { if exist, err := userExists(testUser); err != nil && exist { t.Fatalf("test user should not exist") } if err := createGoogleUser(testUser); err != nil { t.Errorf("createGoogleUser failed creating test user") } if exist, err := userExists(testUser); exist != true || err != nil { t.Errorf("test user should exist") } cmd := exec.Command("groups", testUser) ret := runCmdOutput(cmd) if ret.ExitCode() != 0 { t.Errorf("failed looking up groups for user: stdout:%s stderr:%s", ret.Stdout(), ret.Stderr()) } groups := strings.Split(strings.TrimSpace(strings.Split(ret.Stdout(), ":")[1]), " ") expectedGroupString := config.Section("Accounts").Key("groups").MustString(defaultgroupstring) expectedGroups := strings.Split(expectedGroupString, ",") for _, group := range groups { if !contains(group, expectedGroups) { t.Errorf("test user has been added to an unexpected group %s", group) } } if _, err := os.Stat(fmt.Sprintf("/home/%s", testUser)); err != nil { t.Errorf("test user home directory does not exist") } if err := createGoogleUser(testUser); err == nil { t.Errorf("createGoogleUser did not return error when creating user that already exists") } if err := removeGoogleUser(testUser); err != nil { t.Errorf("removeGoogleUser did not remove user") } if exist, err := userExists(testUser); err != nil && exist == true { t.Errorf("test user should not exist") } if err := removeGoogleUser(testUser); err == nil { t.Errorf("removeGoogleUser did not return error when removing user that doesn't exist") } } func TestGroupaddDuplicates(t *testing.T) { cmd := exec.Command("groupadd", "integ-test-group") ret := runCmdOutput(cmd) if ret.ExitCode() != 0 { t.Fatalf("got wrong exit code running \"groupadd integ-test-group\", expected 0 got %v\n", ret.ExitCode()) } cmd = exec.Command("groupadd", "integ-test-group") ret = runCmdOutput(cmd) if ret.ExitCode() != 9 { t.Fatalf("got wrong exit code running \"groupadd integ-test-group\", expected 9 got %v\n", ret.ExitCode()) } } func contains(target string, expected []string) bool { for _, e := range expected { if e == target { return true } } return false } guest-agent-20231004.02/google_guest_agent/osinfo/000077500000000000000000000000001450737260700215635ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/osinfo/osinfo.go000066400000000000000000000024361450737260700234140ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package osinfo import ( "fmt" ) // OSInfo contains OS information about the system. type OSInfo struct { // OS name in short form. OS string // OS version ID. VersionID string // The name the OS uses to fully describe itself. PrettyName string // The kernel release. KernelRelease string // The kernel version. KernelVersion string // This is used by oslogin.go Version Ver } // Ver describes the system version type Ver struct { Major, Minor, Patch, Length int } func (v Ver) String() string { if v.Major == 0 { return "" } ret := fmt.Sprintf("%d", v.Major) if v.Length > 1 { ret = fmt.Sprintf("%s.%d", ret, v.Minor) } if v.Length > 2 { ret = fmt.Sprintf("%s.%d", ret, v.Patch) } return ret } guest-agent-20231004.02/google_guest_agent/osinfo/osinfo_unix.go000066400000000000000000000070651450737260700244620ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. //go:build unix package osinfo import ( "bytes" "errors" "fmt" "os" "strconv" "strings" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "golang.org/x/sys/unix" ) var ( osRelease = "/etc/os-release" systemRelease = "/etc/system-release" ) func parseOSRelease(osRelease string) (OSInfo, error) { var ret OSInfo for _, line := range strings.Split(osRelease, "\n") { var id = line if id = strings.TrimPrefix(line, "ID="); id != line { id = strings.Trim(id, `"`) ret.OS = parseID(id) } if id = strings.TrimPrefix(line, "VERSION_ID="); id != line { id = strings.Trim(id, `"`) ret.VersionID = id version, err := parseVersion(id) if err != nil { return ret, fmt.Errorf("couldn't parse version id: %v", err) } ret.Version = version } if name := strings.TrimPrefix(line, "PRETTY_NAME="); name != line { name = strings.Trim(name, `"`) ret.PrettyName = name } } return ret, nil } func parseSystemRelease(systemRelease string) (OSInfo, error) { var ret OSInfo var key = " release " idx := strings.Index(systemRelease, key) if idx == -1 { return ret, errors.New("SystemRelease does not match expected format") } ret.OS = parseID(systemRelease[:idx]) versionFromRelease := strings.Split(systemRelease[idx+len(key):], " ")[0] version, err := parseVersion(versionFromRelease) if err != nil { return ret, fmt.Errorf("couldn't parse version: %v", err) } ret.Version = version return ret, nil } func parseVersion(version string) (Ver, error) { versionparts := strings.Split(version, ".") ret := Ver{Length: len(versionparts)} // Must have at least major version. var err error ret.Major, err = strconv.Atoi(versionparts[0]) if err != nil { return ret, err } if ret.Length > 1 { ret.Minor, err = strconv.Atoi(versionparts[1]) if err != nil { return ret, err } } if ret.Length > 2 { ret.Patch, err = strconv.Atoi(versionparts[2]) if err != nil { return ret, err } } return ret, nil } func parseID(id string) string { switch id { case "Red Hat Enterprise Linux Server": return "rhel" case "CentOS", "CentOS Linux": return "centos" default: return id } } func parseRelease() (OSInfo, error) { if releaseFile, err := os.ReadFile(osRelease); err == nil { return parseOSRelease(string(releaseFile)) } if releaseFile, err := os.ReadFile(systemRelease); err == nil { return parseSystemRelease(string(releaseFile)) } return OSInfo{}, errors.New("no known release file found") } // Get returns OSInfo on the running system. func Get() OSInfo { osInfo, err := parseRelease() if err != nil { // This is a non critical error, we can still return a partially populated OSInfo. logger.Warningf("Error parsing release info: %v", err) } var uts unix.Utsname if err := unix.Uname(&uts); err != nil { logger.Warningf("unix.Uname error: %v", err) return osInfo } osInfo.KernelVersion = string(bytes.TrimRight(uts.Version[:], "\x00")) osInfo.KernelRelease = string(bytes.TrimRight(uts.Release[:], "\x00")) return osInfo } guest-agent-20231004.02/google_guest_agent/osinfo/osinfo_unix_test.go000066400000000000000000000062021450737260700255110ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. //go:build unix package osinfo import ( "reflect" "testing" ) func TestParseSystemRelease(t *testing.T) { tests := []struct { desc string file string want OSInfo wantErr bool }{ {"rhel 6.10", "Red Hat Enterprise Linux Server release 6.10 (Santiago)", OSInfo{OS: "rhel", Version: Ver{6, 10, 0, 2}}, false}, {"rhel 6.10.1", "Red Hat Enterprise Linux Server release 6.10.1", OSInfo{OS: "rhel", Version: Ver{6, 10, 1, 3}}, false}, {"centos 7.6.1810", "CentOS Linux release 7.6.1810 (Core)", OSInfo{OS: "centos", Version: Ver{7, 6, 1810, 3}}, false}, {"bad format", "CentOS Linux", OSInfo{}, true}, {"bad version", "CentOS Linux release Core", OSInfo{OS: "centos"}, true}, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { got, err := parseSystemRelease(tc.file) if !reflect.DeepEqual(got, tc.want) { t.Errorf("parseSystemRelease(%s) incorrect return: got %v, want %v", tc.file, got, tc.want) } if (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) { t.Errorf("want error return: %T, got error: %v", tc.wantErr, err) } }) } } func TestParseOSRelease(t *testing.T) { tests := []struct { desc string file string want OSInfo wantErr bool }{ {"sles 12", "ID=\"sles\"\nPRETTY_NAME=\"SLES\"\nVERSION=\"12-SP4\"\nVERSION_ID=12", OSInfo{OS: "sles", PrettyName: "SLES", VersionID: "12", Version: Ver{12, 0, 0, 1}}, false}, {"sles 12.4", "ID=sles\nPRETTY_NAME=\"SLES\"\nVERSION=\"12-SP4\"\nVERSION_ID=\"12.4\"", OSInfo{OS: "sles", PrettyName: "SLES", VersionID: "12.4", Version: Ver{12, 4, 0, 2}}, false}, {"debian 9 (stretch)", "ID=debian\nPRETTY_NAME=\"Debian GNU/Linux\"\nVERSION=\"9 (stretch)\"\nVERSION_ID=\"9\"", OSInfo{OS: "debian", PrettyName: "Debian GNU/Linux", VersionID: "9", Version: Ver{9, 0, 0, 1}}, false}, {"debian 9", "ID=\"debian\"\nPRETTY_NAME=\"Debian GNU/Linux\"\nVERSION=9\nVERSION_ID=\"9\"", OSInfo{OS: "debian", VersionID: "9", PrettyName: "Debian GNU/Linux", Version: Ver{9, 0, 0, 1}}, false}, {"error version parsing", "ID=\"debian\"\nPRETTY_NAME=\"Debian GNU/Linux\"\nVERSION=9\nVERSION_ID=\"something\"", OSInfo{OS: "debian", PrettyName: "Debian GNU/Linux", VersionID: "something"}, true}, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { got, err := parseOSRelease(tc.file) if !reflect.DeepEqual(got, tc.want) { t.Errorf("parseOSRelease(%s) incorrect return: got %+v, want %+v", tc.file, got, tc.want) } if (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) { t.Errorf("want error return: %T, got error: %v", tc.wantErr, err) } }) } } guest-agent-20231004.02/google_guest_agent/osinfo/osinfo_windows.go000066400000000000000000000130571450737260700251670ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package osinfo import ( "errors" "fmt" "os" "path/filepath" "strings" "syscall" "unsafe" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) var ( versionDLL = windows.NewLazySystemDLL("version.dll") // https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-getfileversioninfosizew procGetFileVersionInfoSizeW = versionDLL.NewProc("GetFileVersionInfoSizeW") // https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-getfileversioninfow procGetFileVersionInfoW = versionDLL.NewProc("GetFileVersionInfoW") // https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-verqueryvaluew procVerQueryValueW = versionDLL.NewProc("VerQueryValueW") ) // getTranslation returns the anguage and code page identifier from the provided // version-information block. func getTranslation(block []byte) (string, error) { var start uint var length uint blockStart := uintptr(unsafe.Pointer(&block[0])) if ret, _, _ := procVerQueryValueW.Call( blockStart, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(`\VarFileInfo\Translation`))), uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&length))); ret == 0 { return "", errors.New("zero return code from VerQueryValueW indicates failure") } begin := int(start) - int(blockStart) // For translation data length is bytes. trans := block[begin : begin+int(length)] // Each 'translation' is 4 bytes long (2 16-bit sections), we just want the // first one for simplicity. t := make([]byte, 4) // 16-bit language ID little endian // https://msdn.microsoft.com/en-us/library/windows/desktop/dd318693(v=vs.85).aspx t[0], t[1] = trans[1], trans[0] // 16-bit code page ID little endian // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx t[2], t[3] = trans[3], trans[2] return fmt.Sprintf("%x", t), nil } // getStringFileInfo returns the string value file info name specific to the language and code page indicated. func getStringFileInfo(block []byte, langCodePage, name string) (string, error) { var start uint var length uint blockStart := uintptr(unsafe.Pointer(&block[0])) if ret, _, _ := procVerQueryValueW.Call( blockStart, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fmt.Sprintf(`\StringFileInfo\%s\%s`, langCodePage, name)))), uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&length))); ret == 0 { return "", errors.New("zero return code from VerQueryValueW indicates failure") } begin := int(start) - int(blockStart) // For version information length is characters (UTF16). result := block[begin : begin+int(2*length)] // Result is UTF16LE. u16s := make([]uint16, length) for i := range u16s { u16s[i] = uint16(result[i*2+1])<<8 | uint16(result[i*2]) } return syscall.UTF16ToString(u16s), nil } func getVersion(block []byte, langCodePage string) (string, string, error) { ver, err := getStringFileInfo(block, langCodePage, "FileVersion") if err != nil { return "", "", err } rel, err := getStringFileInfo(block, langCodePage, "ProductVersion") return ver, rel, err } func getKernelInfo() (string, string, error) { root := os.Getenv("SystemRoot") if root == "" { root = `C:\Windows` } path := filepath.Join(root, "System32", "ntoskrnl.exe") if _, err := os.Stat(path); err != nil { return "", "", err } pPtr := unsafe.Pointer(syscall.StringToUTF16Ptr(path)) size, _, _ := procGetFileVersionInfoSizeW.Call( uintptr(pPtr)) if size <= 0 { return "", "", errors.New("GetFileVersionInfoSize call failed, data size can not be 0") } info := make([]byte, size) if ret, _, _ := procGetFileVersionInfoW.Call( uintptr(pPtr), 0, uintptr(len(info)), uintptr(unsafe.Pointer(&info[0]))); ret == 0 { return "", "", errors.New("zero return code from GetFileVersionInfoW indicates failure") } // This should be something like 040904b0 for US English UTF16LE. langCodePage, err := getTranslation(info) if err != nil { return "", "", fmt.Errorf("getTranslation() error: %v", err) } return getVersion(info, langCodePage) } // Get returns OSInfo on the running system. func Get() OSInfo { var osInfo OSInfo osInfo.OS = "windows" kVersion, kRelease, err := getKernelInfo() if err != nil { logger.Warningf("getKernelInfo() error: %v", err) return osInfo } osInfo.KernelVersion = kVersion osInfo.KernelRelease = kRelease // SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion is always 6.3 now, so we get os version from kernel release vs := strings.Split(kRelease, ".") if len(vs) == 4 { osInfo.VersionID = strings.Join(vs[:3], ".") } k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) if err != nil { logger.Warningf("registry.OpenKey error: %v", err) return osInfo } defer k.Close() productName, _, err := k.GetStringValue("ProductName") if err != nil { logger.Warningf("GetStringValue('ProductName') error: %v", err) return osInfo } osInfo.PrettyName = productName return osInfo } guest-agent-20231004.02/google_guest_agent/oslogin.go000066400000000000000000000313561450737260700222770ustar00rootroot00000000000000// Copyright 2019 Google LLC // 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 // https://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. package main import ( "context" "fmt" "os" "os/exec" "runtime" "strings" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/sshtrustedca" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) var ( googleComment = "# Added by Google Compute Engine OS Login." googleBlockStart = "#### Google OS Login control. Do not edit this section. ####" googleBlockEnd = "#### End Google OS Login control section. ####" ) type osloginMgr struct{} // We also read project keys first, letting instance-level keys take // precedence. func getOSLoginEnabled(md *metadata.Descriptor) (bool, bool, bool) { var enable bool if md.Project.Attributes.EnableOSLogin != nil { enable = *md.Project.Attributes.EnableOSLogin } if md.Instance.Attributes.EnableOSLogin != nil { enable = *md.Instance.Attributes.EnableOSLogin } var twofactor bool if md.Project.Attributes.TwoFactor != nil { twofactor = *md.Project.Attributes.TwoFactor } if md.Instance.Attributes.TwoFactor != nil { twofactor = *md.Instance.Attributes.TwoFactor } var skey bool if md.Project.Attributes.SecurityKey != nil { skey = *md.Project.Attributes.SecurityKey } if md.Instance.Attributes.SecurityKey != nil { skey = *md.Instance.Attributes.SecurityKey } return enable, twofactor, skey } func (o *osloginMgr) Diff(ctx context.Context) (bool, error) { oldEnable, oldTwoFactor, oldSkey := getOSLoginEnabled(oldMetadata) enable, twofactor, skey := getOSLoginEnabled(newMetadata) return oldMetadata.Project.ProjectID == "" || // True on first run or if any value has changed. (oldTwoFactor != twofactor) || (oldEnable != enable) || (oldSkey != skey), nil } func (o *osloginMgr) Timeout(ctx context.Context) (bool, error) { return false, nil } func (o *osloginMgr) Disabled(ctx context.Context) (bool, error) { return runtime.GOOS == "windows", nil } func (o *osloginMgr) Set(ctx context.Context) error { // We need to know if it was previously enabled for the clearing of // metadata-based SSH keys. oldEnable, _, _ := getOSLoginEnabled(oldMetadata) enable, twofactor, skey := getOSLoginEnabled(newMetadata) if enable && !oldEnable { logger.Infof("Enabling OS Login") newMetadata.Instance.Attributes.SSHKeys = nil newMetadata.Project.Attributes.SSHKeys = nil (&accountsMgr{}).Set(ctx) } if !enable && oldEnable { logger.Infof("Disabling OS Login") } if err := writeSSHConfig(enable, twofactor, skey); err != nil { logger.Errorf("Error updating SSH config: %v.", err) } if err := writeNSSwitchConfig(enable); err != nil { logger.Errorf("Error updating NSS config: %v.", err) } if err := writePAMConfig(enable, twofactor); err != nil { logger.Errorf("Error updating PAM config: %v.", err) } if err := writeGroupConf(enable); err != nil { logger.Errorf("Error updating group.conf: %v.", err) } for _, svc := range []string{"nscd", "unscd", "systemd-logind", "cron", "crond"} { // These services should be restarted if running logger.Debugf("systemctl try-restart %s, if it exists", svc) if err := systemctlTryRestart(ctx, svc); err != nil { logger.Errorf("Error restarting service: %v.", err) } } // SSH should be started if not running, reloaded otherwise. for _, svc := range []string{"ssh", "sshd"} { logger.Debugf("systemctl reload-or-restart %s, if it exists", svc) if err := systemctlReloadOrRestart(ctx, svc); err != nil { logger.Errorf("Error reloading service: %v.", err) } } now := fmt.Sprintf("%d", time.Now().Unix()) mdsClient.WriteGuestAttributes(ctx, "guest-agent/sshable", now) if enable { logger.Debugf("Create OS Login dirs, if needed") if err := createOSLoginDirs(ctx); err != nil { logger.Errorf("Error creating OS Login directory: %v.", err) } logger.Debugf("create OS Login sudoers config, if needed") if err := createOSLoginSudoersFile(); err != nil { logger.Errorf("Error creating OS Login sudoers file: %v.", err) } logger.Debugf("starting OS Login nss cache fill") if err := run.Quiet(ctx, "google_oslogin_nss_cache"); err != nil { logger.Errorf("Error updating NSS cache: %v.", err) } } return nil } func filterGoogleLines(contents string) []string { var isgoogle, isgoogleblock bool var filtered []string for _, line := range strings.Split(contents, "\n") { switch { case strings.Contains(line, googleComment) && !isgoogleblock: isgoogle = true case strings.Contains(line, googleBlockEnd): isgoogleblock = false isgoogle = false case isgoogleblock, strings.Contains(line, googleBlockStart): isgoogleblock = true case isgoogle: isgoogle = false default: filtered = append(filtered, line) } } return filtered } func writeConfigFile(path, contents string) error { logger.Debugf("writing %s", path) file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0777) if err != nil { return err } defer closeFile(file) file.WriteString(contents) return nil } func updateSSHConfig(sshConfig string, enable, twofactor, skey bool) string { // TODO: this feels like a case for a text/template challengeResponseEnable := "ChallengeResponseAuthentication yes" authorizedKeysCommand := "AuthorizedKeysCommand /usr/bin/google_authorized_keys" if skey { authorizedKeysCommand = "AuthorizedKeysCommand /usr/bin/google_authorized_keys_sk" } if runtime.GOOS == "freebsd" { authorizedKeysCommand = "AuthorizedKeysCommand /usr/local/bin/google_authorized_keys" if skey { authorizedKeysCommand = "AuthorizedKeysCommand /usr/local/bin/google_authorized_keys_sk" } } authorizedKeysUser := "AuthorizedKeysCommandUser root" // Certificate based authentication. authorizedPrincipalsCommand := "AuthorizedPrincipalsCommand /usr/bin/google_authorized_principals %u %k" authorizedPrincipalsUser := "AuthorizedPrincipalsCommandUser root" trustedUserCAKeys := "TrustedUserCAKeys " + sshtrustedca.DefaultPipePath twoFactorAuthMethods := "AuthenticationMethods publickey,keyboard-interactive" if (osInfo.OS == "rhel" || osInfo.OS == "centos") && osInfo.Version.Major == 6 { authorizedKeysUser = "AuthorizedKeysCommandRunAs root" twoFactorAuthMethods = "RequiredAuthentications2 publickey,keyboard-interactive" } matchblock1 := `Match User sa_*` matchblock2 := ` AuthenticationMethods publickey` filtered := filterGoogleLines(string(sshConfig)) if enable { osLoginBlock := []string{googleBlockStart} if cfg.Get().OSLogin.CertAuthentication { osLoginBlock = append(osLoginBlock, trustedUserCAKeys, authorizedPrincipalsCommand, authorizedPrincipalsUser) } osLoginBlock = append(osLoginBlock, authorizedKeysCommand, authorizedKeysUser) if twofactor { osLoginBlock = append(osLoginBlock, twoFactorAuthMethods, challengeResponseEnable) } osLoginBlock = append(osLoginBlock, googleBlockEnd) filtered = append(osLoginBlock, filtered...) if twofactor { filtered = append(filtered, googleBlockStart, matchblock1, matchblock2, googleBlockEnd) } } return strings.Join(filtered, "\n") } func writeSSHConfig(enable, twofactor, skey bool) error { sshConfig, err := os.ReadFile("/etc/ssh/sshd_config") if err != nil { return err } proposed := updateSSHConfig(string(sshConfig), enable, twofactor, skey) if proposed == string(sshConfig) { return nil } return writeConfigFile("/etc/ssh/sshd_config", proposed) } func updateNSSwitchConfig(nsswitch string, enable bool) string { oslogin := " cache_oslogin oslogin" var filtered []string for _, line := range strings.Split(string(nsswitch), "\n") { if strings.HasPrefix(line, "passwd:") || strings.HasPrefix(line, "group:") { present := strings.Contains(line, "oslogin") if enable && !present { line += oslogin } else if !enable && present { line = strings.TrimSuffix(line, oslogin) } if runtime.GOOS == "freebsd" { line = strings.Replace(line, "compat", "files", 1) } } filtered = append(filtered, line) } return strings.Join(filtered, "\n") } func writeNSSwitchConfig(enable bool) error { nsswitch, err := os.ReadFile("/etc/nsswitch.conf") if err != nil { return err } proposed := updateNSSwitchConfig(string(nsswitch), enable) if proposed == string(nsswitch) { return nil } return writeConfigFile("/etc/nsswitch.conf", proposed) } func updatePAMsshdPamless(pamsshd string, enable, twofactor bool) string { authOSLogin := "auth [success=done perm_denied=die default=ignore] pam_oslogin_login.so" authGroup := "auth [default=ignore] pam_group.so" sessionHomeDir := "session [success=ok default=ignore] pam_mkhomedir.so" if runtime.GOOS == "freebsd" { authOSLogin = "auth optional pam_oslogin_login.so" authGroup = "auth optional pam_group.so" sessionHomeDir = "session optional pam_mkhomedir.so" } filtered := filterGoogleLines(string(pamsshd)) if enable { topOfFile := []string{googleBlockStart} if twofactor { topOfFile = append(topOfFile, authOSLogin) } topOfFile = append(topOfFile, authGroup, googleBlockEnd) bottomOfFile := []string{googleBlockStart, sessionHomeDir, googleBlockEnd} filtered = append(topOfFile, filtered...) filtered = append(filtered, bottomOfFile...) } return strings.Join(filtered, "\n") } func writePAMConfig(enable, twofactor bool) error { pamsshd, err := os.ReadFile("/etc/pam.d/sshd") if err != nil { return err } proposed := updatePAMsshdPamless(string(pamsshd), enable, twofactor) if proposed != string(pamsshd) { if err := writeConfigFile("/etc/pam.d/sshd", proposed); err != nil { return err } } return nil } func updateGroupConf(groupconf string, enable bool) string { config := "sshd;*;*;Al0000-2400;video\n" filtered := filterGoogleLines(groupconf) if enable { filtered = append(filtered, []string{googleComment, config}...) } return strings.Join(filtered, "\n") } func writeGroupConf(enable bool) error { groupconf, err := os.ReadFile("/etc/security/group.conf") if err != nil { return err } proposed := updateGroupConf(string(groupconf), enable) if proposed != string(groupconf) { if err := writeConfigFile("/etc/security/group.conf", proposed); err != nil { return err } } return nil } // Creates necessary OS Login directories if they don't exist. func createOSLoginDirs(ctx context.Context) error { restorecon, restoreconerr := exec.LookPath("restorecon") for _, dir := range []string{"/var/google-sudoers.d", "/var/google-users.d"} { err := os.Mkdir(dir, 0750) if err != nil && !os.IsExist(err) { return err } if restoreconerr == nil { run.Quiet(ctx, restorecon, dir) } } return nil } func createOSLoginSudoersFile() error { osloginSudoers := "/etc/sudoers.d/google-oslogin" if runtime.GOOS == "freebsd" { osloginSudoers = "/usr/local" + osloginSudoers } sudoFile, err := os.OpenFile(osloginSudoers, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0440) if err != nil { if os.IsExist(err) { return nil } return err } fmt.Fprintf(sudoFile, "#includedir /var/google-sudoers.d\n") return sudoFile.Close() } // systemctlTryRestart tries to restart a systemd service if it is already // running. Stopped services will be ignored. func systemctlTryRestart(ctx context.Context, servicename string) error { if !systemctlUnitExists(ctx, servicename) { return nil } return run.Quiet(ctx, "systemctl", "try-restart", servicename+".service") } // systemctlReloadOrRestart tries to reload a running systemd service if // supported, restart otherwise. Stopped services will be started. func systemctlReloadOrRestart(ctx context.Context, servicename string) error { if !systemctlUnitExists(ctx, servicename) { return nil } return run.Quiet(ctx, "systemctl", "reload-or-restart", servicename+".service") } // systemctlStart tries to start a stopped systemd service. Started services // will be ignored. func systemctlStart(ctx context.Context, servicename string) error { if !systemctlUnitExists(ctx, servicename) { return nil } return run.Quiet(ctx, "systemctl", "start", servicename+".service") } func systemctlUnitExists(ctx context.Context, servicename string) bool { res := run.WithOutput(ctx, "systemctl", "list-units", "--all", servicename+".service") return !strings.Contains(res.StdOut, "0 loaded units listed") } guest-agent-20231004.02/google_guest_agent/oslogin_test.go000066400000000000000000000335401450737260700233330ustar00rootroot00000000000000// Copyright 2019 Google LLC // 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 // https://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. package main import ( "encoding/json" "fmt" "strings" "testing" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/sshtrustedca" "github.com/GoogleCloudPlatform/guest-agent/metadata" ) func TestFilterGoogleLines(t *testing.T) { cmpslice := func(a, b []string) bool { if len(a) != len(b) { return false } for idx := 0; idx < len(a); idx++ { if a[idx] != b[idx] { return false } } return true } var tests = []struct { contents, want []string }{ { []string{ "line1", "line2", googleComment, "line3 after google comment", "line4", googleBlockStart, "line5 inside google block", "line6 inside google block", googleBlockEnd, "line7", }, []string{ "line1", "line2", "line4", "line7", }, }, { []string{ "line1", "line2", googleBlockEnd, "line3", "line4", }, []string{ "line1", "line2", "line3", "line4", }, }, { []string{ googleBlockStart, "line1 inside google block", "line2 inside google block", googleBlockEnd, "line3", }, []string{ "line3", }, }, { []string{ googleBlockStart, "line1 inside google block", googleBlockStart, "line2 inside google block", googleBlockEnd, "line3", googleBlockEnd, "line4", }, []string{ "line3", "line4", }, }, { []string{ googleBlockEnd, googleBlockStart, "line1 inside google block", "line2 inside google block", googleComment, googleBlockEnd, "line3", }, []string{ "line3", }, }, } for idx, tt := range tests { if res := filterGoogleLines(strings.Join(tt.contents, "\n")); !cmpslice(res, tt.want) { t.Errorf("test %v\nwant:\n%v\ngot:\n%v\n", idx, tt.want, res) } } } func TestUpdateNSSwitchConfig(t *testing.T) { oslogin := " cache_oslogin oslogin" var tests = []struct { contents, want []string enable bool }{ { contents: []string{ "line1", "passwd: line2", "group: line3", }, want: []string{ "line1", "passwd: line2" + oslogin, "group: line3" + oslogin, }, enable: true, }, { contents: []string{ "line1", "passwd: line2" + oslogin, "group: line3" + oslogin, }, want: []string{ "line1", "passwd: line2", "group: line3", }, enable: false, }, { contents: []string{ "line1", "passwd: line2" + oslogin, "group: line3" + oslogin, }, want: []string{ "line1", "passwd: line2" + oslogin, "group: line3" + oslogin, }, enable: true, }, } for idx, tt := range tests { contents := strings.Join(tt.contents, "\n") want := strings.Join(tt.want, "\n") if res := updateNSSwitchConfig(contents, tt.enable); res != want { t.Errorf("test %v\nwant:\n%v\ngot:\n%v\n", idx, want, res) } } } func TestUpdateSSHConfig(t *testing.T) { challengeResponseEnable := "ChallengeResponseAuthentication yes" authorizedKeysCommand := "AuthorizedKeysCommand /usr/bin/google_authorized_keys" authorizedKeysCommandSk := "AuthorizedKeysCommand /usr/bin/google_authorized_keys_sk" authorizedKeysUser := "AuthorizedKeysCommandUser root" authorizedPrincipalsCommand := "AuthorizedPrincipalsCommand /usr/bin/google_authorized_principals %u %k" authorizedPrincipalsUser := "AuthorizedPrincipalsCommandUser root" trustedUserCAKeys := "TrustedUserCAKeys " + sshtrustedca.DefaultPipePath twoFactorAuthMethods := "AuthenticationMethods publickey,keyboard-interactive" matchblock1 := `Match User sa_*` matchblock2 := ` AuthenticationMethods publickey` var tests = []struct { contents, want []string enable, twofactor, skey, cert bool }{ { // Full block is created, any others removed. contents: []string{ "line1", googleBlockStart, "line2", googleBlockEnd, }, want: []string{ googleBlockStart, trustedUserCAKeys, authorizedPrincipalsCommand, authorizedPrincipalsUser, authorizedKeysCommand, authorizedKeysUser, twoFactorAuthMethods, challengeResponseEnable, googleBlockEnd, "line1", googleBlockStart, matchblock1, matchblock2, googleBlockEnd, }, enable: true, twofactor: true, skey: false, cert: true, }, { // Full block is created, any others removed. contents: []string{ "line1", googleBlockStart, "line2", googleBlockEnd, }, want: []string{ googleBlockStart, authorizedKeysCommand, authorizedKeysUser, twoFactorAuthMethods, challengeResponseEnable, googleBlockEnd, "line1", googleBlockStart, matchblock1, matchblock2, googleBlockEnd, }, enable: true, twofactor: true, skey: false, cert: false, }, { // Full block is created, google comments removed. contents: []string{ "line1", googleComment, "line2", "line3", }, want: []string{ googleBlockStart, trustedUserCAKeys, authorizedPrincipalsCommand, authorizedPrincipalsUser, authorizedKeysCommand, authorizedKeysUser, twoFactorAuthMethods, challengeResponseEnable, googleBlockEnd, "line1", "line3", googleBlockStart, matchblock1, matchblock2, googleBlockEnd, }, enable: true, twofactor: true, skey: false, cert: true, }, { // Full block is created, google comments removed. contents: []string{ "line1", googleComment, "line2", "line3", }, want: []string{ googleBlockStart, authorizedKeysCommand, authorizedKeysUser, twoFactorAuthMethods, challengeResponseEnable, googleBlockEnd, "line1", "line3", googleBlockStart, matchblock1, matchblock2, googleBlockEnd, }, enable: true, twofactor: true, skey: false, cert: false, }, { // Block is created without two-factor options. contents: []string{ "line1", "line2", }, want: []string{ googleBlockStart, trustedUserCAKeys, authorizedPrincipalsCommand, authorizedPrincipalsUser, authorizedKeysCommand, authorizedKeysUser, googleBlockEnd, "line1", "line2", }, enable: true, twofactor: false, skey: false, cert: true, }, { // Block is created without two-factor options. contents: []string{ "line1", "line2", }, want: []string{ googleBlockStart, authorizedKeysCommand, authorizedKeysUser, googleBlockEnd, "line1", "line2", }, enable: true, twofactor: false, skey: false, cert: false, }, { // Existing block is removed. contents: []string{ "line1", "line2", googleBlockStart, "line3", googleBlockEnd, }, want: []string{ "line1", "line2", }, enable: false, twofactor: true, skey: false, cert: true, }, { // Existing block is removed. contents: []string{ "line1", "line2", googleBlockStart, "line3", googleBlockEnd, }, want: []string{ "line1", "line2", }, enable: false, twofactor: true, skey: false, cert: false, }, { // Skey binary is chosen instead. contents: []string{ "line1", "line2", googleBlockStart, "line3", googleBlockEnd, }, want: []string{ googleBlockStart, trustedUserCAKeys, authorizedPrincipalsCommand, authorizedPrincipalsUser, authorizedKeysCommandSk, authorizedKeysUser, googleBlockEnd, "line1", "line2", }, enable: true, twofactor: false, skey: true, cert: true, }, { // Skey binary is chosen instead. contents: []string{ "line1", "line2", googleBlockStart, "line3", googleBlockEnd, }, want: []string{ googleBlockStart, authorizedKeysCommandSk, authorizedKeysUser, googleBlockEnd, "line1", "line2", }, enable: true, twofactor: false, skey: true, cert: false, }, } if err := cfg.Load(nil); err != nil { t.Fatalf("Failed to initialize configuration manager: %+v", err) } config := cfg.Get() defaultCertAuthConfig := config.OSLogin.CertAuthentication for idx, tt := range tests { contents := strings.Join(tt.contents, "\n") want := strings.Join(tt.want, "\n") config.OSLogin.CertAuthentication = tt.cert if res := updateSSHConfig(contents, tt.enable, tt.twofactor, tt.skey); res != want { t.Errorf("test %v\nwant:\n%v\ngot:\n%v\n", idx, want, res) } } config.OSLogin.CertAuthentication = defaultCertAuthConfig } func TestUpdatePAMsshdPamless(t *testing.T) { authOSLogin := "auth [success=done perm_denied=die default=ignore] pam_oslogin_login.so" authGroup := "auth [default=ignore] pam_group.so" sessionHomeDir := "session [success=ok default=ignore] pam_mkhomedir.so" var tests = []struct { contents, want []string enable, twofactor bool }{ { contents: []string{ "line1", "line2", }, want: []string{ googleBlockStart, authOSLogin, authGroup, googleBlockEnd, "line1", "line2", googleBlockStart, sessionHomeDir, googleBlockEnd, }, enable: true, twofactor: true, }, { contents: []string{ "line1", "line2", }, want: []string{ googleBlockStart, authGroup, googleBlockEnd, "line1", "line2", googleBlockStart, sessionHomeDir, googleBlockEnd, }, enable: true, twofactor: false, }, { contents: []string{ googleBlockStart, "line1", googleBlockEnd, "line2", googleBlockStart, "line3", googleBlockEnd, }, want: []string{ "line2", }, enable: false, twofactor: true, }, } for idx, tt := range tests { t.Run(fmt.Sprintf("test-%d", idx), func(t *testing.T) { contents := strings.Join(tt.contents, "\n") want := strings.Join(tt.want, "\n") if res := updatePAMsshdPamless(contents, tt.enable, tt.twofactor); res != want { t.Errorf("want:\n%v\ngot:\n%v\n", want, res) } }) } } func TestUpdateGroupConf(t *testing.T) { config := "sshd;*;*;Al0000-2400;video" var tests = []struct { contents, want []string enable bool }{ { contents: []string{ "line1", "line2", }, want: []string{ "line1", "line2", googleComment, config, "", }, enable: true, }, { contents: []string{ "line1", "line2", }, want: []string{ "line1", "line2", }, enable: false, }, { contents: []string{ "line1", "line2", googleComment, "line3", // not the right line }, want: []string{ "line1", "line2", googleComment, config, "", }, enable: true, }, { contents: []string{ "line1", "line2", googleComment, "line3", }, want: []string{ "line1", "line2", }, enable: false, }, } for idx, tt := range tests { contents := strings.Join(tt.contents, "\n") want := strings.Join(tt.want, "\n") if res := updateGroupConf(contents, tt.enable); res != want { t.Errorf("test %v\nwant:\n%v\ngot:\n%v\n", idx, want, res) } } } func TestGetOSLoginEnabled(t *testing.T) { var tests = []struct { md string enable, twofactor, skey bool }{ { md: `{"instance": {"attributes": {"enable-oslogin": "true", "enable-oslogin-2fa": "true"}}}`, enable: true, twofactor: true, skey: false, }, { md: `{"project": {"attributes": {"enable-oslogin": "true", "enable-oslogin-2fa": "true"}}}`, enable: true, twofactor: true, skey: false, }, { // Instance keys take precedence md: `{"project": {"attributes": {"enable-oslogin": "false", "enable-oslogin-2fa": "false"}}, "instance": {"attributes": {"enable-oslogin": "true", "enable-oslogin-2fa": "true"}}}`, enable: true, twofactor: true, skey: false, }, { // Instance keys take precedence md: `{"project": {"attributes": {"enable-oslogin": "true", "enable-oslogin-2fa": "true"}}, "instance": {"attributes": {"enable-oslogin": "false", "enable-oslogin-2fa": "false"}}}`, enable: false, twofactor: false, skey: false, }, { // Handle weird values md: `{"instance": {"attributes": {"enable-oslogin": "TRUE", "enable-oslogin-2fa": "foobar"}}}`, enable: true, twofactor: false, skey: false, }, { // Mixed test md: `{"project": {"attributes": {"enable-oslogin": "true", "enable-oslogin-2fa": "true"}}, "instance": {"attributes": {"enable-oslogin-2fa": "false"}}}`, enable: true, twofactor: false, skey: false, }, { // Skey test md: `{"instance": {"attributes": {"enable-oslogin": "true", "enable-oslogin-2fa": "true", "enable-oslogin-sk": "true"}}}`, enable: true, twofactor: true, skey: true, }, } for idx, tt := range tests { var md metadata.Descriptor if err := json.Unmarshal([]byte(tt.md), &md); err != nil { t.Errorf("Failed to unmarshal metadata JSON for test %v: %v", idx, err) } enable, twofactor, skey := getOSLoginEnabled(&md) if enable != tt.enable || twofactor != tt.twofactor || skey != tt.skey { t.Errorf("Test %v failed. Expected: %v/%v/%v Got: %v/%v/%v", idx, tt.enable, tt.twofactor, tt.skey, enable, twofactor, skey) } } } guest-agent-20231004.02/google_guest_agent/run/000077500000000000000000000000001450737260700210725ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/run/run.go000066400000000000000000000062161450737260700222320ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package run is a package with utilities for running command and handling results. package run import ( "bytes" "context" "errors" "os/exec" "strings" "time" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) // Result wraps a command execution result. type Result struct { // Exit code. Set to -1 if we failed to run the command. ExitCode int // Stderr or err.Error if we failed to run the command. StdErr string // Stdout or "" if we failed to run the command. StdOut string // Combined is the process' stdout and stderr combined. Combined string } // Error return an error containing the stderr content. func (e Result) Error() string { return strings.TrimSuffix(e.StdErr, "\n") } // Quiet runs a command and doesn't return a result, but an error in case of failure. func Quiet(ctx context.Context, name string, args ...string) error { res := execCommand(exec.CommandContext(ctx, name, args...)) if res.ExitCode != 0 { return res } return nil } // WithOutput runs a command and returns the result. func WithOutput(ctx context.Context, name string, args ...string) *Result { return execCommand(exec.CommandContext(ctx, name, args...)) } // WithOutputTimeout runs a command with a defined timeout and returns its result. func WithOutputTimeout(ctx context.Context, timeout time.Duration, name string, args ...string) *Result { child, cancel := context.WithTimeout(ctx, timeout) defer cancel() res := execCommand(exec.CommandContext(child, name, args...)) if child.Err() != nil && errors.Is(child.Err(), context.DeadlineExceeded) { res.ExitCode = 124 // By convention } return res } // WithCombinedOutput returns a result with stderr and stdout combined in the Combined // member of Result. func WithCombinedOutput(ctx context.Context, name string, args ...string) *Result { cmd := exec.CommandContext(ctx, name, args...) output, err := cmd.CombinedOutput() if err != nil { exitCode := -1 if ee, ok := err.(*exec.ExitError); ok { exitCode = ee.ExitCode() } return &Result{ ExitCode: exitCode, StdErr: err.Error(), } } return &Result{ Combined: string(output), } } func execCommand(cmd *exec.Cmd) *Result { var stdout, stderr bytes.Buffer logger.Debugf("exec: %v", cmd) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { if ee, ok := err.(*exec.ExitError); ok { return &Result{ ExitCode: ee.ExitCode(), StdOut: stdout.String(), StdErr: stderr.String(), } } return &Result{ ExitCode: -1, StdErr: err.Error(), } } return &Result{ ExitCode: 0, StdOut: stdout.String(), } } guest-agent-20231004.02/google_guest_agent/run/run_test.go000066400000000000000000000134001450737260700232620ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package run import ( "context" "fmt" "os" "path" "strings" "testing" "time" ) // builds a set of data to be used in the tests func buildDataContent(t *testing.T) string { t.Helper() rootDir := path.Join(t.TempDir(), fmt.Sprintf("%d", time.Now().UnixNano())) if err := os.MkdirAll(rootDir, 0744); err != nil { t.Fatalf("Failed to make test dir: %+v", err) } if err := os.WriteFile(path.Join(rootDir, "data"), []byte("random data"), 0644); err != nil { t.Fatalf("Failed to write test data: %+v", err) } return rootDir } func TestQuietSuccess(t *testing.T) { testDir := buildDataContent(t) tests := []string{ "grep -R data " + testDir, "echo 'foobar' >> " + path.Join(testDir, "foobar"), "rm -Rf " + path.Join(testDir, "foobar"), "echo", } for _, curr := range tests { t.Run(curr, func(t *testing.T) { tokens := strings.Split(curr, " ") if err := Quiet(context.Background(), tokens[0], tokens[1:]...); err != nil { t.Errorf("run.Quiet(%s) failed with error: %+v, expected success.", curr, err) } }) } } func TestQuietFail(t *testing.T) { testDir := buildDataContent(t) tests := []string{ "grep -R datax " + testDir, "rm -R /root/data", } for _, curr := range tests { t.Run(curr, func(t *testing.T) { tokens := strings.Split(curr, " ") if err := Quiet(context.Background(), tokens[0], tokens[1:]...); err == nil { t.Errorf("run.Quiet(%s) command succeed, expected failure.", curr) } }) } } func TestOutputSuccess(t *testing.T) { testDir := buildDataContent(t) tests := []struct { cmd string output string }{ {"grep -R data " + testDir, path.Join(testDir, "data") + ":random data\n"}, {"echo foobar", "foobar\n"}, {"echo -n foobar", "foobar"}, {"cat " + path.Join(testDir, "data"), "random data"}, } for _, curr := range tests { t.Run(curr.cmd, func(t *testing.T) { tokens := strings.Split(curr.cmd, " ") res := WithOutput(context.Background(), tokens[0], tokens[1:]...) if res.ExitCode != 0 { t.Errorf("run.WithOutput(%s) failed with exitCode: %b, expected success.", curr, res.ExitCode) } if res.StdOut != curr.output { t.Errorf("run.WithOutput(%s) failed with stdout: %s, expected empty stdout.", curr.cmd, res.StdOut) } }) } } func TestOutputFail(t *testing.T) { testDir := buildDataContent(t) tests := []string{ "grep -R foobar " + testDir, "cat /root/foobar", } for _, curr := range tests { t.Run(curr, func(t *testing.T) { tokens := strings.Split(curr, " ") res := WithOutput(context.Background(), tokens[0], tokens[1:]...) if res.ExitCode == 0 { t.Errorf("run.WithOutput(%s) command succeeded, expected failure.", curr) } }) } } func TestCombinedOutputSuccess(t *testing.T) { testDir := buildDataContent(t) tests := []struct { cmd string output string }{ {"grep -R data " + testDir, path.Join(testDir, "data") + ":random data\n"}, {"echo foobar", "foobar\n"}, {"echo -n foobar", "foobar"}, {"cat " + path.Join(testDir, "data"), "random data"}, } for _, curr := range tests { t.Run(curr.cmd, func(t *testing.T) { tokens := strings.Split(curr.cmd, " ") res := WithCombinedOutput(context.Background(), tokens[0], tokens[1:]...) if res.ExitCode != 0 { t.Errorf("run.WithCombinedOutput(%s) failed with exitCode: %b, expected success.", curr, res.ExitCode) } if res.Combined != curr.output { t.Errorf("run.WithCombinedOutput(%s) failed with stdout: %s, expected empty stdout.", curr.cmd, res.StdOut) } }) } } func TestCombinedOutputFail(t *testing.T) { testDir := buildDataContent(t) tests := []string{ "grep -R foobar " + testDir, "cat /root/foobar", } for _, curr := range tests { t.Run(curr, func(t *testing.T) { tokens := strings.Split(curr, " ") res := WithCombinedOutput(context.Background(), tokens[0], tokens[1:]...) if res.ExitCode == 0 { t.Errorf("run.WithCombinedoutput(%s) command succeeded, expected failure.", curr) } }) } } func TestOutputTimeoutSuccess(t *testing.T) { testDir := buildDataContent(t) tests := []struct { cmd string output string }{ {"grep -R data " + testDir, path.Join(testDir, "data") + ":random data\n"}, {"echo foobar", "foobar\n"}, {"echo -n foobar", "foobar"}, {"cat " + path.Join(testDir, "data"), "random data"}, } for _, curr := range tests { t.Run(curr.cmd, func(t *testing.T) { tokens := strings.Split(curr.cmd, " ") res := WithOutputTimeout(context.Background(), 1*time.Second, tokens[0], tokens[1:]...) if res.ExitCode != 0 { t.Errorf("run.WithOutputTimeout(%s) command failed with exitcode: %d, expected 0.", curr, res.ExitCode) } if res.StdOut != curr.output { t.Errorf("run.WithOutputTimeout(%s) command failed with stdout: %s, expected empty stdout.", curr.cmd, res.StdOut) } }) } } func TestOutputTimeoutFail(t *testing.T) { testDir := buildDataContent(t) tests := []string{ "grep -R foobar " + testDir, "cat /root/foobar", } for _, curr := range tests { t.Run(curr, func(t *testing.T) { tokens := strings.Split(curr, " ") res := WithOutputTimeout(context.Background(), 1*time.Second, tokens[0], tokens[1:]...) if res.ExitCode == 0 { t.Errorf("run.WithOutputTimeout(%s) command succeeded, expected failure.", curr) } }) } } guest-agent-20231004.02/google_guest_agent/scheduler/000077500000000000000000000000001450737260700222445ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/scheduler/logger.go000066400000000000000000000016431450737260700240560ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package scheduler import ( "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) type cronLogger struct{} func (cl *cronLogger) Info(msg string, keysAndValues ...any) { logger.Infof("%s: %+v", msg, keysAndValues) } func (cl *cronLogger) Error(err error, msg string, keysAndValues ...any) { logger.Infof("%s: %+v", msg, keysAndValues) } guest-agent-20231004.02/google_guest_agent/scheduler/scheduler.go000066400000000000000000000120511450737260700245500ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package scheduler maintains scheduler utility for scheduling arbitrary jobs. package scheduler import ( "context" "fmt" "sync" "time" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/robfig/cron/v3" ) // Job defines the interface between the schedule manager and the actual job. type Job interface { // ID returns the job id. ID() string // Interval returns the interval at which job should be rescheduled and // a bool determining if job should be scheduled starting now. // If false, first run will be at time now+interval. Interval() (time.Duration, bool) // ShouldEnable specifies if the job should be enabled for scheduling. ShouldEnable(context.Context) bool // Run triggers the job for single execution. It returns error if any // and a bool stating if scheduler should continue or stop scheduling. Run(context.Context) (bool, error) } // Scheduler implements job schedule manager and offers a way to schedule/unschedule new jobs. type Scheduler struct { cron *cron.Cron jobs map[string]cron.EntryID mu sync.RWMutex } var scheduler *Scheduler func init() { taskIDs := make(map[string]cron.EntryID) cron := cron.New(cron.WithLogger(&cronLogger{})) scheduler = &Scheduler{ cron: cron, jobs: taskIDs, mu: sync.RWMutex{}, } } // Get starts and returns scheduler instance. func Get() *Scheduler { scheduler.start() return scheduler } // getFunc generates a wrapper function for cron scheduler. func (s *Scheduler) getFunc(ctx context.Context, job Job) func() { f := func() { schedule, err := job.Run(ctx) if !schedule { s.UnscheduleJob(job.ID()) } if err != nil { logger.Errorf("Failed to execute job %s: %v", job.ID(), err) } } return f } // ScheduleJob adds a job to schedule at defined interval. func (s *Scheduler) ScheduleJob(ctx context.Context, job Job, synchronous bool) error { if !job.ShouldEnable(ctx) { return fmt.Errorf("ShouldEnable() returned false, cannot schedule job %s", job.ID()) } logger.Infof("Scheduling job: %s", job.ID()) interval, startNow := job.Interval() if err := s.jobInit(job.ID(), interval, s.getFunc(ctx, job), startNow, synchronous); err != nil { return err } return nil } func (s *Scheduler) setEntryID(jobID string, entryID cron.EntryID) { s.mu.Lock() defer s.mu.Unlock() s.jobs[jobID] = entryID } // jobInit adds job to the schedule to run at specified interval. // Setting startImmediately to true executes first run immediately, otherwise // first run will be after interval (at now+interval). // If startImmediately and synchronous both are true, init method will block // until job is completed. func (s *Scheduler) jobInit(jobID string, interval time.Duration, job func(), startImmediately, synchronous bool) error { logger.Infof("Scheduling job %q to run at %f hr interval", jobID, interval.Hours()) _, found := s.jobs[jobID] // If found, job is already running, return. if found { logger.Infof("Skipping, job %q is already scheduled", jobID) return nil } entry, err := s.cron.AddFunc(fmt.Sprintf("@every %ds", int(interval.Seconds())), job) if err != nil { return fmt.Errorf("unable to schedule %q: %w", jobID, err) } s.setEntryID(jobID, entry) if startImmediately { if synchronous { job() } else { // Start job in a go routine to not block the caller. go job() } } return nil } // UnscheduleJob removes the job from schedule. func (s *Scheduler) UnscheduleJob(jobID string) { s.mu.RLock() defer s.mu.RUnlock() logger.Infof("Unscheduling job %q", jobID) entry, found := s.jobs[jobID] if found { s.cron.Remove(entry) delete(s.jobs, jobID) } } // start begins executing each job at defined interval. func (s *Scheduler) start() { logger.Infof("Starting the scheduler to run jobs") s.cron.Start() } // Stop stops executing new jobs. func (s *Scheduler) Stop() { logger.Infof("Stopping the scheduler") s.cron.Stop() } // ScheduleJobs schedules required jobs and waits for it to finish if synchronous is true. func ScheduleJobs(ctx context.Context, jobs []Job, synchronous bool) { wg := sync.WaitGroup{} sched := Get() var ids []string for _, job := range jobs { wg.Add(1) ids = append(ids, job.ID()) go func(job Job) { defer wg.Done() if err := sched.ScheduleJob(ctx, job, synchronous); err != nil { logger.Errorf("Failed to schedule job %s with error: %v", job.ID(), err) } else { logger.Infof("Successfully scheduled job %s", job.ID()) } }(job) } if synchronous { logger.Debugf("Waiting for %v to finish...", ids) wg.Wait() } } guest-agent-20231004.02/google_guest_agent/scheduler/scheduler_test.go000066400000000000000000000122021450737260700256050ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package scheduler import ( "context" "testing" "time" ) type testJob struct { interval time.Duration shouldEnable bool startingNow bool id string ctr int stopAfter int } func (j *testJob) Run(_ context.Context) (bool, error) { j.ctr++ if j.ctr == j.stopAfter { return false, nil } return true, nil } func (j *testJob) ID() string { return j.id } func (j *testJob) Interval() (time.Duration, bool) { return j.interval, j.startingNow } func (j *testJob) ShouldEnable(_ context.Context) bool { return j.shouldEnable } func TestSchedule(t *testing.T) { job := &testJob{ interval: time.Second / 2, id: "test_job", shouldEnable: true, startingNow: true, ctr: 0, } s := Get() if err := s.ScheduleJob(context.Background(), job, false); err != nil { t.Errorf("AddJob(%s) failed unexecptedly with error: %v", job.ID(), err) } s.start() if _, ok := s.jobs[job.ID()]; !ok { t.Errorf("Failed to schedule %s, expected an entry in scheduled jobs", job.ID()) } time.Sleep(3 * time.Second) s.Stop() if job.ctr < 4 { t.Errorf("Scheduler failed to schedule job, counter value found %d, expcted atleast 3", job.ctr) } } func TestMultipleSchedules(t *testing.T) { ctx := context.Background() job1 := &testJob{ interval: time.Second / 2, id: "test_job1", shouldEnable: true, startingNow: true, ctr: 0, } job2 := &testJob{ interval: time.Second / 2, id: "test_job2", shouldEnable: true, startingNow: true, ctr: 0, } s := Get() defer s.Stop() // Schedule multiple jobs. if err := s.ScheduleJob(ctx, job1, false); err != nil { t.Errorf("AddJob(%s) failed unexecptedly with error: %v", job1.id, err) } if err := s.ScheduleJob(ctx, job2, false); err != nil { t.Errorf("AddJob(%s) failed unexecptedly with error: %v", job2.id, err) } time.Sleep(2 * time.Second) s.UnscheduleJob(job2.ID()) if _, ok := s.jobs[job1.ID()]; !ok { t.Errorf("Failed to schedule %s, expected an entry in scheduled jobs", job1.ID()) } if _, ok := s.jobs[job2.ID()]; ok { t.Errorf("Failed to unschedule %s, found an entry in scheduled jobs", job2.ID()) } time.Sleep(time.Second) // Verify job1 is still running and job2 is unscheduled. if job1.ctr < 4 { t.Errorf("Scheduler failed to schedule job, counter value found %d, expcted atleast 3", job1.ctr) } if job2.ctr > 3 { t.Errorf("Scheduler failed to unschedule job, counter value found %d, expcted less than 3", job2.ctr) } } func TestStopSchedule(t *testing.T) { s := Get() job := &testJob{ interval: time.Second / 2, id: "test_job", shouldEnable: true, startingNow: true, stopAfter: 2, ctr: 0, } if err := s.ScheduleJob(context.Background(), job, false); err != nil { t.Errorf("AddJob(%s) failed unexecptedly with error: %v", job.ID(), err) } if _, ok := s.jobs[job.ID()]; !ok { t.Errorf("Failed to schedule %s, expected an entry in scheduled jobs", job.ID()) } time.Sleep(3 * time.Second) if job.ctr > 3 { t.Errorf("Scheduler failed to stop the job, counter value found %d, should have stopped after max 3", job.ctr) } } func TestScheduleJobError(t *testing.T) { job := &testJob{ interval: time.Second / 2, id: "test_job", shouldEnable: false, } s := Get() if err := s.ScheduleJob(context.Background(), job, false); err == nil { t.Errorf("ScheduleJob(ctx, %s) succeeded unexpectedly when shouldEnable set to false, want error", job.ID()) } } type testLongJob struct { id string sleepFor time.Duration } func (j *testLongJob) Run(_ context.Context) (bool, error) { time.Sleep(j.sleepFor) return false, nil } func (j *testLongJob) ID() string { return j.id } func (j *testLongJob) Interval() (time.Duration, bool) { return 2 * time.Minute, true } func (j *testLongJob) ShouldEnable(_ context.Context) bool { return true } func TestScheduleJobsWait(t *testing.T) { ctx := context.Background() start := time.Now().Second() ScheduleJobs(ctx, []Job{&testLongJob{id: "job1", sleepFor: time.Second}}, true) end := time.Now().Second() want := 1 if got := end - start; got < want { t.Errorf("ScheduleJobs(ctx, job1, true) returned after %d seconds, expected to wait for %d", got, want) } } func TestScheduleJobsNoWait(t *testing.T) { ctx := context.Background() start := time.Now().Second() ScheduleJobs(ctx, []Job{&testLongJob{id: "job1", sleepFor: time.Second}}, false) end := time.Now().Second() if got := end - start; got >= 1 { t.Errorf("ScheduleJobs(ctx, job1, true) returned after %d seconds, expected no wait", got) } } guest-agent-20231004.02/google_guest_agent/service.go000066400000000000000000000050611450737260700222570ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "context" "fmt" "os" "path/filepath" "time" "github.com/kardianos/service" ) type program struct { run func(context.Context) ctx context.Context cancel context.CancelFunc done chan struct{} timeout time.Duration } func (p *program) Start(s service.Service) error { go func() { p.run(p.ctx) close(p.done) }() return nil } func (p *program) Stop(s service.Service) error { p.cancel() select { case <-p.done: return nil case <-time.After(p.timeout): return fmt.Errorf("failed to shutdown within timeout %s", p.timeout) } } func usage(name string) { fmt.Printf( "Usage:\n"+ " %[1]s install: install the %[2]s service\n"+ " %[1]s remove: remove the %[2]s service\n"+ " %[1]s start: start the %[2]s service\n"+ " %[1]s stop: stop the %[2]s service\n", filepath.Base(os.Args[0]), name) } func register(ctx context.Context, name, displayName, desc string, run func(context.Context), action string) error { svcConfig := &service.Config{ Name: name, DisplayName: displayName, Description: desc, } ctx, cancel := context.WithCancel(ctx) done := make(chan struct{}) prg := &program{ run: run, ctx: ctx, cancel: cancel, done: done, timeout: 15 * time.Second, } svc, err := service.New(prg, svcConfig) if err != nil { return err } switch action { case "run": return svc.Run() case "install": if err := svc.Install(); err != nil { return fmt.Errorf("failed to install service %s: %s", name, err) } case "remove": if err := svc.Uninstall(); err != nil { return fmt.Errorf("failed to remove service %s: %s", name, err) } case "start": if err := svc.Start(); err != nil { return fmt.Errorf("failed to start service %s: %s", name, err) } case "stop": if err := svc.Stop(); err != nil { return fmt.Errorf("failed to stop service %s: %s", name, err) } case "help": usage(name) default: fmt.Printf("%q is not a valid argument.\n", action) usage(name) } return nil } guest-agent-20231004.02/google_guest_agent/snapshot_listener.go000066400000000000000000000146721450737260700243730ustar00rootroot00000000000000// Copyright 2020 Google LLC // 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 // https://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. package main import ( "context" "fmt" "os" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" sspb "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/snapshot_service" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/golang/groupcache/lru" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) var ( scriptsDir = "/etc/google/snapshots/" seenPreSnapshotOperationIds = lru.New(128) seenPostSnapshotOperationIds = lru.New(128) maxRequestHandleAttempts = 10 // completely arbitrary max attempt ) type snapshotConfig struct { timeout time.Duration // seconds } func getSnapshotConfig(timeoutInSeconds int) (snapshotConfig, error) { var conf snapshotConfig conf.timeout = time.Duration(timeoutInSeconds) * time.Second return conf, nil } func runScript(ctx context.Context, path, disks string, config snapshotConfig) (int, sspb.AgentErrorCode) { logger.Infof("Running guest consistent snapshot script at: %s.", path) if _, err := os.Stat(path); os.IsNotExist(err) { return -1, sspb.AgentErrorCode_SCRIPT_NOT_FOUND } execResult := run.WithOutputTimeout(ctx, config.timeout, path, disks) if execResult.ExitCode == 124 { return execResult.ExitCode, sspb.AgentErrorCode_SCRIPT_TIMED_OUT } if execResult.ExitCode != 0 { return execResult.ExitCode, sspb.AgentErrorCode_UNHANDLED_SCRIPT_ERROR } return execResult.ExitCode, sspb.AgentErrorCode_NO_ERROR } func listenForSnapshotRequests(ctx context.Context, address string, requestChan chan<- *sspb.GuestMessage) { for { // Start hanging connection on server that feeds to channel logger.Infof("Attempting to connect to snapshot service at %s.", address) conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { logger.Errorf("Failed to connect to snapshot service: %v.", err) return } c := sspb.NewSnapshotServiceClient(conn) ctx, cancel := context.WithCancel(ctx) guestReady := sspb.GuestReady{ RequestServerInfo: false, } r, err := c.CreateConnection(ctx, &guestReady) if err != nil { logger.Errorf("Error creating connection: %v.", err) cancel() continue } for { request, err := r.Recv() if err != nil { logger.Errorf("Error reading snapshot request: %v.", err) cancel() break } logger.Infof("Received snapshot request.") requestChan <- request } } } func getSnapshotResponse(ctx context.Context, timeoutInSeconds int, guestMessage *sspb.GuestMessage) *sspb.SnapshotResponse { switch { case guestMessage.GetSnapshotRequest() != nil: request := guestMessage.GetSnapshotRequest() response := &sspb.SnapshotResponse{ OperationId: request.GetOperationId(), Type: request.GetType(), } config, err := getSnapshotConfig(timeoutInSeconds) if err != nil { response.AgentReturnCode = sspb.AgentErrorCode_INVALID_CONFIG return response } var url string switch request.GetType() { case sspb.OperationType_PRE_SNAPSHOT: logger.Infof("Handling pre snapshot request for operation id %d.", request.GetOperationId()) _, found := seenPreSnapshotOperationIds.Get(request.GetOperationId()) if found { logger.Infof("Duplicate pre snapshot request operation id %d.", request.GetOperationId()) return nil } seenPreSnapshotOperationIds.Add(request.GetOperationId(), request.GetOperationId()) url = scriptsDir + "pre.sh" case sspb.OperationType_POST_SNAPSHOT: logger.Infof("Handling post snapshot request for operation id %d.", request.GetOperationId()) _, found := seenPostSnapshotOperationIds.Get(request.GetOperationId()) if found { logger.Infof("Duplicate post snapshot request operation id %d.", request.GetOperationId()) return nil } seenPostSnapshotOperationIds.Add(request.GetOperationId(), request.GetOperationId()) url = scriptsDir + "post.sh" default: logger.Errorf("Unhandled operation type %d.", request.GetType()) return nil } scriptsReturnCode, agentErrorCode := runScript(ctx, url, request.GetDiskList(), config) response.ScriptsReturnCode = int32(scriptsReturnCode) response.AgentReturnCode = agentErrorCode return response default: } return nil } func handleSnapshotRequests(ctx context.Context, timeoutInSeconds int, address string, requestChan <-chan *sspb.GuestMessage) { for { conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()) if err != nil { logger.Errorf("Failed to connect to snapshot service: %v.", err) return } for { // Listen on channel and respond guestMessage := <-requestChan response := getSnapshotResponse(ctx, timeoutInSeconds, guestMessage) // We either got a duplicated pre/post or an invalid request // in both cases we want to ignore it. if response == nil { continue } for i := 0; i < maxRequestHandleAttempts; i++ { logger.Infof("Attempt %d/%d of handling snapshot request.", i+1, maxRequestHandleAttempts) c := sspb.NewSnapshotServiceClient(conn) ctx, cancel := context.WithCancel(ctx) defer cancel() _, err = c.HandleResponsesFromGuest(ctx, response) if err != nil { logger.Errorf("Error sending response: %v.", err) time.Sleep(1 * time.Second) // Avoid idle looping continue } logger.Debugf("Successfully handled snapshot request.") break } } } } func startSnapshotListener(ctx context.Context, snapshotServiceIP string, snapshotServicePort int, timeoutInSeconds int) { requestChan := make(chan *sspb.GuestMessage) address := fmt.Sprintf("%s:%d", snapshotServiceIP, snapshotServicePort) // Create scripts directory if it doesn't exist. _, err := os.Stat(scriptsDir) if os.IsNotExist(err) { // Make the directory only readable/writable/executable by root. os.MkdirAll(scriptsDir, 0700) } go listenForSnapshotRequests(ctx, address, requestChan) go handleSnapshotRequests(ctx, timeoutInSeconds, address, requestChan) } guest-agent-20231004.02/google_guest_agent/snapshot_service/000077500000000000000000000000001450737260700236455ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/snapshot_service/snapshot_service.proto000066400000000000000000000047621450737260700303220ustar00rootroot00000000000000// Copyright 2019 Google LLC // 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 // https://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. syntax = "proto3"; package cloud.vmm; enum OperationType { NOT_SET = 0; PRE_SNAPSHOT = 1; POST_SNAPSHOT = 2; } enum SupportedFeatures { NONE = 0; SNAPSHOTS = 1; } message SnapshotRequest { // The operation id of the snapshot. int32 operation_id = 1; // A list of comma separated target/lun values, e.g "1/2,3/4". string disk_list = 2; // The operation type. OperationType type = 3; } message ServerInfo { repeated SupportedFeatures supported_features = 1; } enum AgentErrorCode { NO_ERROR = 0; // The snapshot config was improper in some way. INVALID_CONFIG = 1; // The pre or post snapshot script was not found on disk. SCRIPT_NOT_FOUND = 2; // The pre or post snapshot script timed out. SCRIPT_TIMED_OUT = 3; // The pre or post snapshot script returned an error, but the "continue on // error" flag was not set. UNHANDLED_SCRIPT_ERROR = 4; } message SnapshotResponse { // The operation id of the snapshot. int32 operation_id = 1; // The return code of the scripts run by the guest. If this is non-zero, then // agent_return_code should be UNHANDLED_SCRIPT_ERROR. int32 scripts_return_code = 2; // The agent return code. AgentErrorCode agent_return_code = 3; // The operation type. OperationType type = 4; } message GuestReady { bool request_server_info = 1; } message GuestMessage { oneof msg { SnapshotRequest snapshot_request = 1; ServerInfo server_info = 2; } } message ServerAck {} // Service to handle pre and post snapshot requests from vanadium. service SnapshotService { // The client is expected to first send a "GuestReady" message, indicating // they are available and establishing the connection that vanadium writes // requests to the agent on. rpc CreateConnection(GuestReady) returns (stream GuestMessage) {} // Handles the agent's responses to the above requests. rpc HandleResponsesFromGuest(SnapshotResponse) returns (ServerAck) {} } guest-agent-20231004.02/google_guest_agent/snapshot_service/snapshot_service.proto.pb.go000066400000000000000000000556301450737260700313260ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // source: snapshot_service.proto package cloud_vmm import ( context "context" fmt "fmt" proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type OperationType int32 const ( OperationType_NOT_SET OperationType = 0 OperationType_PRE_SNAPSHOT OperationType = 1 OperationType_POST_SNAPSHOT OperationType = 2 ) var OperationType_name = map[int32]string{ 0: "NOT_SET", 1: "PRE_SNAPSHOT", 2: "POST_SNAPSHOT", } var OperationType_value = map[string]int32{ "NOT_SET": 0, "PRE_SNAPSHOT": 1, "POST_SNAPSHOT": 2, } func (x OperationType) String() string { return proto.EnumName(OperationType_name, int32(x)) } func (OperationType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{0} } type SupportedFeatures int32 const ( SupportedFeatures_NONE SupportedFeatures = 0 SupportedFeatures_SNAPSHOTS SupportedFeatures = 1 ) var SupportedFeatures_name = map[int32]string{ 0: "NONE", 1: "SNAPSHOTS", } var SupportedFeatures_value = map[string]int32{ "NONE": 0, "SNAPSHOTS": 1, } func (x SupportedFeatures) String() string { return proto.EnumName(SupportedFeatures_name, int32(x)) } func (SupportedFeatures) EnumDescriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{1} } type AgentErrorCode int32 const ( AgentErrorCode_NO_ERROR AgentErrorCode = 0 // The snapshot config was improper in some way. AgentErrorCode_INVALID_CONFIG AgentErrorCode = 1 // The pre or post snapshot script was not found on disk. AgentErrorCode_SCRIPT_NOT_FOUND AgentErrorCode = 2 // The pre or post snapshot script timed out. AgentErrorCode_SCRIPT_TIMED_OUT AgentErrorCode = 3 // The pre or post snapshot script returned an error, but the "continue on // error" flag was not set. AgentErrorCode_UNHANDLED_SCRIPT_ERROR AgentErrorCode = 4 ) var AgentErrorCode_name = map[int32]string{ 0: "NO_ERROR", 1: "INVALID_CONFIG", 2: "SCRIPT_NOT_FOUND", 3: "SCRIPT_TIMED_OUT", 4: "UNHANDLED_SCRIPT_ERROR", } var AgentErrorCode_value = map[string]int32{ "NO_ERROR": 0, "INVALID_CONFIG": 1, "SCRIPT_NOT_FOUND": 2, "SCRIPT_TIMED_OUT": 3, "UNHANDLED_SCRIPT_ERROR": 4, } func (x AgentErrorCode) String() string { return proto.EnumName(AgentErrorCode_name, int32(x)) } func (AgentErrorCode) EnumDescriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{2} } type SnapshotRequest struct { // The operation id of the snapshot. OperationId int32 `protobuf:"varint,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"` // A list of comma separated target/lun values, e.g "1/2,3/4". DiskList string `protobuf:"bytes,2,opt,name=disk_list,json=diskList,proto3" json:"disk_list,omitempty"` // The operation type. Type OperationType `protobuf:"varint,3,opt,name=type,proto3,enum=cloud.vmm.OperationType" json:"type,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *SnapshotRequest) Reset() { *m = SnapshotRequest{} } func (m *SnapshotRequest) String() string { return proto.CompactTextString(m) } func (*SnapshotRequest) ProtoMessage() {} func (*SnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{0} } func (m *SnapshotRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SnapshotRequest.Unmarshal(m, b) } func (m *SnapshotRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_SnapshotRequest.Marshal(b, m, deterministic) } func (m *SnapshotRequest) XXX_Merge(src proto.Message) { xxx_messageInfo_SnapshotRequest.Merge(m, src) } func (m *SnapshotRequest) XXX_Size() int { return xxx_messageInfo_SnapshotRequest.Size(m) } func (m *SnapshotRequest) XXX_DiscardUnknown() { xxx_messageInfo_SnapshotRequest.DiscardUnknown(m) } var xxx_messageInfo_SnapshotRequest proto.InternalMessageInfo func (m *SnapshotRequest) GetOperationId() int32 { if m != nil { return m.OperationId } return 0 } func (m *SnapshotRequest) GetDiskList() string { if m != nil { return m.DiskList } return "" } func (m *SnapshotRequest) GetType() OperationType { if m != nil { return m.Type } return OperationType_NOT_SET } type ServerInfo struct { SupportedFeatures []SupportedFeatures `protobuf:"varint,1,rep,packed,name=supported_features,json=supportedFeatures,proto3,enum=cloud.vmm.SupportedFeatures" json:"supported_features,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *ServerInfo) Reset() { *m = ServerInfo{} } func (m *ServerInfo) String() string { return proto.CompactTextString(m) } func (*ServerInfo) ProtoMessage() {} func (*ServerInfo) Descriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{1} } func (m *ServerInfo) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ServerInfo.Unmarshal(m, b) } func (m *ServerInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_ServerInfo.Marshal(b, m, deterministic) } func (m *ServerInfo) XXX_Merge(src proto.Message) { xxx_messageInfo_ServerInfo.Merge(m, src) } func (m *ServerInfo) XXX_Size() int { return xxx_messageInfo_ServerInfo.Size(m) } func (m *ServerInfo) XXX_DiscardUnknown() { xxx_messageInfo_ServerInfo.DiscardUnknown(m) } var xxx_messageInfo_ServerInfo proto.InternalMessageInfo func (m *ServerInfo) GetSupportedFeatures() []SupportedFeatures { if m != nil { return m.SupportedFeatures } return nil } type SnapshotResponse struct { // The operation id of the snapshot. OperationId int32 `protobuf:"varint,1,opt,name=operation_id,json=operationId,proto3" json:"operation_id,omitempty"` // The return code of the scripts run by the guest. If this is non-zero, then // agent_return_code should be UNHANDLED_SCRIPT_ERROR. ScriptsReturnCode int32 `protobuf:"varint,2,opt,name=scripts_return_code,json=scriptsReturnCode,proto3" json:"scripts_return_code,omitempty"` // The agent return code. AgentReturnCode AgentErrorCode `protobuf:"varint,3,opt,name=agent_return_code,json=agentReturnCode,proto3,enum=cloud.vmm.AgentErrorCode" json:"agent_return_code,omitempty"` // The operation type. Type OperationType `protobuf:"varint,4,opt,name=type,proto3,enum=cloud.vmm.OperationType" json:"type,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *SnapshotResponse) Reset() { *m = SnapshotResponse{} } func (m *SnapshotResponse) String() string { return proto.CompactTextString(m) } func (*SnapshotResponse) ProtoMessage() {} func (*SnapshotResponse) Descriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{2} } func (m *SnapshotResponse) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SnapshotResponse.Unmarshal(m, b) } func (m *SnapshotResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_SnapshotResponse.Marshal(b, m, deterministic) } func (m *SnapshotResponse) XXX_Merge(src proto.Message) { xxx_messageInfo_SnapshotResponse.Merge(m, src) } func (m *SnapshotResponse) XXX_Size() int { return xxx_messageInfo_SnapshotResponse.Size(m) } func (m *SnapshotResponse) XXX_DiscardUnknown() { xxx_messageInfo_SnapshotResponse.DiscardUnknown(m) } var xxx_messageInfo_SnapshotResponse proto.InternalMessageInfo func (m *SnapshotResponse) GetOperationId() int32 { if m != nil { return m.OperationId } return 0 } func (m *SnapshotResponse) GetScriptsReturnCode() int32 { if m != nil { return m.ScriptsReturnCode } return 0 } func (m *SnapshotResponse) GetAgentReturnCode() AgentErrorCode { if m != nil { return m.AgentReturnCode } return AgentErrorCode_NO_ERROR } func (m *SnapshotResponse) GetType() OperationType { if m != nil { return m.Type } return OperationType_NOT_SET } type GuestReady struct { RequestServerInfo bool `protobuf:"varint,1,opt,name=request_server_info,json=requestServerInfo,proto3" json:"request_server_info,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *GuestReady) Reset() { *m = GuestReady{} } func (m *GuestReady) String() string { return proto.CompactTextString(m) } func (*GuestReady) ProtoMessage() {} func (*GuestReady) Descriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{3} } func (m *GuestReady) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GuestReady.Unmarshal(m, b) } func (m *GuestReady) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_GuestReady.Marshal(b, m, deterministic) } func (m *GuestReady) XXX_Merge(src proto.Message) { xxx_messageInfo_GuestReady.Merge(m, src) } func (m *GuestReady) XXX_Size() int { return xxx_messageInfo_GuestReady.Size(m) } func (m *GuestReady) XXX_DiscardUnknown() { xxx_messageInfo_GuestReady.DiscardUnknown(m) } var xxx_messageInfo_GuestReady proto.InternalMessageInfo func (m *GuestReady) GetRequestServerInfo() bool { if m != nil { return m.RequestServerInfo } return false } type GuestMessage struct { // Types that are valid to be assigned to Msg: // *GuestMessage_SnapshotRequest // *GuestMessage_ServerInfo Msg isGuestMessage_Msg `protobuf_oneof:"msg"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *GuestMessage) Reset() { *m = GuestMessage{} } func (m *GuestMessage) String() string { return proto.CompactTextString(m) } func (*GuestMessage) ProtoMessage() {} func (*GuestMessage) Descriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{4} } func (m *GuestMessage) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_GuestMessage.Unmarshal(m, b) } func (m *GuestMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_GuestMessage.Marshal(b, m, deterministic) } func (m *GuestMessage) XXX_Merge(src proto.Message) { xxx_messageInfo_GuestMessage.Merge(m, src) } func (m *GuestMessage) XXX_Size() int { return xxx_messageInfo_GuestMessage.Size(m) } func (m *GuestMessage) XXX_DiscardUnknown() { xxx_messageInfo_GuestMessage.DiscardUnknown(m) } var xxx_messageInfo_GuestMessage proto.InternalMessageInfo type isGuestMessage_Msg interface { isGuestMessage_Msg() } type GuestMessage_SnapshotRequest struct { SnapshotRequest *SnapshotRequest `protobuf:"bytes,1,opt,name=snapshot_request,json=snapshotRequest,proto3,oneof"` } type GuestMessage_ServerInfo struct { ServerInfo *ServerInfo `protobuf:"bytes,2,opt,name=server_info,json=serverInfo,proto3,oneof"` } func (*GuestMessage_SnapshotRequest) isGuestMessage_Msg() {} func (*GuestMessage_ServerInfo) isGuestMessage_Msg() {} func (m *GuestMessage) GetMsg() isGuestMessage_Msg { if m != nil { return m.Msg } return nil } func (m *GuestMessage) GetSnapshotRequest() *SnapshotRequest { if x, ok := m.GetMsg().(*GuestMessage_SnapshotRequest); ok { return x.SnapshotRequest } return nil } func (m *GuestMessage) GetServerInfo() *ServerInfo { if x, ok := m.GetMsg().(*GuestMessage_ServerInfo); ok { return x.ServerInfo } return nil } // XXX_OneofWrappers is for the internal use of the proto package. func (*GuestMessage) XXX_OneofWrappers() []interface{} { return []interface{}{ (*GuestMessage_SnapshotRequest)(nil), (*GuestMessage_ServerInfo)(nil), } } type ServerAck struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *ServerAck) Reset() { *m = ServerAck{} } func (m *ServerAck) String() string { return proto.CompactTextString(m) } func (*ServerAck) ProtoMessage() {} func (*ServerAck) Descriptor() ([]byte, []int) { return fileDescriptor_ef5e407b8dc8e20f, []int{5} } func (m *ServerAck) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ServerAck.Unmarshal(m, b) } func (m *ServerAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_ServerAck.Marshal(b, m, deterministic) } func (m *ServerAck) XXX_Merge(src proto.Message) { xxx_messageInfo_ServerAck.Merge(m, src) } func (m *ServerAck) XXX_Size() int { return xxx_messageInfo_ServerAck.Size(m) } func (m *ServerAck) XXX_DiscardUnknown() { xxx_messageInfo_ServerAck.DiscardUnknown(m) } var xxx_messageInfo_ServerAck proto.InternalMessageInfo func init() { proto.RegisterEnum("cloud.vmm.OperationType", OperationType_name, OperationType_value) proto.RegisterEnum("cloud.vmm.SupportedFeatures", SupportedFeatures_name, SupportedFeatures_value) proto.RegisterEnum("cloud.vmm.AgentErrorCode", AgentErrorCode_name, AgentErrorCode_value) proto.RegisterType((*SnapshotRequest)(nil), "cloud.vmm.SnapshotRequest") proto.RegisterType((*ServerInfo)(nil), "cloud.vmm.ServerInfo") proto.RegisterType((*SnapshotResponse)(nil), "cloud.vmm.SnapshotResponse") proto.RegisterType((*GuestReady)(nil), "cloud.vmm.GuestReady") proto.RegisterType((*GuestMessage)(nil), "cloud.vmm.GuestMessage") proto.RegisterType((*ServerAck)(nil), "cloud.vmm.ServerAck") } func init() { proto.RegisterFile("snapshot_service.proto", fileDescriptor_ef5e407b8dc8e20f) } var fileDescriptor_ef5e407b8dc8e20f = []byte{ // 608 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0xc1, 0x6e, 0xda, 0x40, 0x10, 0xb5, 0x81, 0xb4, 0x30, 0x10, 0xb2, 0x6c, 0xd3, 0x94, 0x92, 0x1e, 0xa8, 0x4f, 0x28, 0x8a, 0x50, 0x45, 0x2f, 0x3d, 0xf4, 0xe2, 0x82, 0x09, 0x56, 0x13, 0x1b, 0xad, 0x9d, 0x4a, 0x3d, 0xad, 0x5c, 0x7b, 0x93, 0x5a, 0x09, 0x5e, 0x77, 0x77, 0x89, 0x94, 0x5e, 0xfa, 0x1b, 0xfd, 0x86, 0x7e, 0x52, 0xbf, 0xa6, 0xb2, 0x31, 0x60, 0x92, 0x1e, 0x72, 0x64, 0xe6, 0xcd, 0x63, 0xde, 0x7b, 0xb3, 0x86, 0x23, 0x99, 0x04, 0xa9, 0xfc, 0xce, 0x15, 0x95, 0x4c, 0xdc, 0xc5, 0x21, 0x1b, 0xa6, 0x82, 0x2b, 0x8e, 0x1b, 0xe1, 0x2d, 0x5f, 0x46, 0xc3, 0xbb, 0xc5, 0xc2, 0xf8, 0x05, 0x07, 0x5e, 0x01, 0x22, 0xec, 0xc7, 0x92, 0x49, 0x85, 0xdf, 0x42, 0x8b, 0xa7, 0x4c, 0x04, 0x2a, 0xe6, 0x09, 0x8d, 0xa3, 0xae, 0xde, 0xd7, 0x07, 0x7b, 0xa4, 0xb9, 0xa9, 0xd9, 0x11, 0x3e, 0x86, 0x46, 0x14, 0xcb, 0x1b, 0x7a, 0x1b, 0x4b, 0xd5, 0xad, 0xf4, 0xf5, 0x41, 0x83, 0xd4, 0xb3, 0xc2, 0x79, 0x2c, 0x15, 0x3e, 0x85, 0x9a, 0xba, 0x4f, 0x59, 0xb7, 0xda, 0xd7, 0x07, 0xed, 0x51, 0x77, 0xb8, 0xf9, 0xb3, 0xa1, 0xbb, 0xa6, 0xf0, 0xef, 0x53, 0x46, 0x72, 0x94, 0xf1, 0x15, 0xc0, 0x63, 0xe2, 0x8e, 0x09, 0x3b, 0xb9, 0xe2, 0xf8, 0x33, 0x60, 0xb9, 0x4c, 0x53, 0x2e, 0x14, 0x8b, 0xe8, 0x15, 0x0b, 0xd4, 0x52, 0x30, 0xd9, 0xd5, 0xfb, 0xd5, 0x41, 0x7b, 0xf4, 0xa6, 0xc4, 0xe4, 0xad, 0x41, 0xd3, 0x02, 0x43, 0x3a, 0xf2, 0x61, 0xc9, 0xf8, 0xab, 0x03, 0xda, 0x8a, 0x93, 0x29, 0x4f, 0x24, 0x7b, 0x8a, 0xba, 0x21, 0xbc, 0x90, 0xa1, 0x88, 0x53, 0x25, 0xa9, 0x60, 0x6a, 0x29, 0x12, 0x1a, 0xf2, 0x88, 0xe5, 0x3a, 0xf7, 0x48, 0xa7, 0x68, 0x91, 0xbc, 0x33, 0xe6, 0x11, 0xc3, 0x16, 0x74, 0x82, 0x6b, 0x96, 0xa8, 0x1d, 0xf4, 0x4a, 0xfd, 0xeb, 0xd2, 0xce, 0x66, 0x86, 0xb1, 0x84, 0xe0, 0x22, 0x9b, 0x22, 0x07, 0xf9, 0x4c, 0x89, 0x66, 0xed, 0x5b, 0xed, 0x49, 0xbe, 0x7d, 0x04, 0x38, 0xcb, 0xe2, 0x22, 0x2c, 0x88, 0xee, 0xb3, 0x95, 0xc5, 0x2a, 0xbe, 0x3c, 0x6a, 0x26, 0x68, 0x9c, 0x5c, 0xf1, 0x5c, 0x5c, 0x9d, 0x74, 0x8a, 0xd6, 0xd6, 0x67, 0xe3, 0xb7, 0x0e, 0xad, 0x7c, 0xfc, 0x82, 0x49, 0x19, 0x5c, 0x33, 0x7c, 0x06, 0x68, 0x73, 0x2c, 0x05, 0x3c, 0x9f, 0x6e, 0x8e, 0x7a, 0x65, 0xdb, 0x77, 0x4f, 0x65, 0xa6, 0x91, 0x03, 0xf9, 0xe0, 0x7a, 0x3e, 0x40, 0xb3, 0xbc, 0x41, 0x25, 0xe7, 0x78, 0x59, 0xe6, 0xd8, 0x6c, 0x31, 0xd3, 0x08, 0xc8, 0xcd, 0xaf, 0x4f, 0x7b, 0x50, 0x5d, 0xc8, 0x6b, 0xa3, 0x09, 0x8d, 0x15, 0xc4, 0x0c, 0x6f, 0x4e, 0x4c, 0xd8, 0xdf, 0x11, 0x8f, 0x9b, 0xf0, 0xdc, 0x71, 0x7d, 0xea, 0x59, 0x3e, 0xd2, 0x30, 0x82, 0xd6, 0x9c, 0x58, 0xd4, 0x73, 0xcc, 0xb9, 0x37, 0x73, 0x7d, 0xa4, 0xe3, 0x0e, 0xec, 0xcf, 0x5d, 0xcf, 0xdf, 0x96, 0x2a, 0x27, 0xa7, 0xd0, 0x79, 0x74, 0x2d, 0xb8, 0x0e, 0x35, 0xc7, 0x75, 0x2c, 0xa4, 0xe1, 0x7d, 0x68, 0xac, 0xc1, 0x1e, 0xd2, 0x4f, 0x7e, 0x42, 0x7b, 0x37, 0x27, 0xdc, 0x82, 0xba, 0xe3, 0x52, 0x8b, 0x10, 0x97, 0x20, 0x0d, 0x63, 0x68, 0xdb, 0xce, 0x17, 0xf3, 0xdc, 0x9e, 0xd0, 0xb1, 0xeb, 0x4c, 0xed, 0x33, 0xa4, 0xe3, 0x43, 0x40, 0xde, 0x98, 0xd8, 0x73, 0x9f, 0x66, 0xab, 0x4d, 0xdd, 0x4b, 0x67, 0x82, 0x2a, 0xa5, 0xaa, 0x6f, 0x5f, 0x58, 0x13, 0xea, 0x5e, 0xfa, 0xa8, 0x8a, 0x7b, 0x70, 0x74, 0xe9, 0xcc, 0x4c, 0x67, 0x72, 0x6e, 0x4d, 0x68, 0xd1, 0x5f, 0x71, 0xd7, 0x46, 0x7f, 0xf4, 0xed, 0x63, 0xf4, 0x56, 0x0f, 0x16, 0x4f, 0x01, 0x8d, 0x05, 0x0b, 0x14, 0x1b, 0xf3, 0x24, 0x61, 0x61, 0xe6, 0x03, 0x2e, 0xbb, 0xb9, 0xbd, 0x81, 0xde, 0xab, 0x87, 0xe5, 0x22, 0x5b, 0x43, 0x7b, 0xa7, 0x63, 0x17, 0xba, 0xb3, 0x20, 0x89, 0x6e, 0xd9, 0xfa, 0x21, 0xc8, 0xa9, 0xe0, 0x8b, 0x1c, 0x86, 0x8f, 0xff, 0x9b, 0xf0, 0x0a, 0xd6, 0x3b, 0x7c, 0x14, 0x9d, 0x19, 0xde, 0x18, 0xda, 0xb7, 0x67, 0xf9, 0xa7, 0xe4, 0xfd, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x11, 0x49, 0x78, 0xe7, 0x64, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // SnapshotServiceClient is the client API for SnapshotService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type SnapshotServiceClient interface { // The client is expected to first send a "GuestReady" message, indicating // they are available and establishing the connection that vanadium writes // requests to the agent on. CreateConnection(ctx context.Context, in *GuestReady, opts ...grpc.CallOption) (SnapshotService_CreateConnectionClient, error) // Handles the agent's responses to the above requests. HandleResponsesFromGuest(ctx context.Context, in *SnapshotResponse, opts ...grpc.CallOption) (*ServerAck, error) } type snapshotServiceClient struct { cc *grpc.ClientConn } func NewSnapshotServiceClient(cc *grpc.ClientConn) SnapshotServiceClient { return &snapshotServiceClient{cc} } func (c *snapshotServiceClient) CreateConnection(ctx context.Context, in *GuestReady, opts ...grpc.CallOption) (SnapshotService_CreateConnectionClient, error) { stream, err := c.cc.NewStream(ctx, &_SnapshotService_serviceDesc.Streams[0], "/cloud.vmm.SnapshotService/CreateConnection", opts...) if err != nil { return nil, err } x := &snapshotServiceCreateConnectionClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type SnapshotService_CreateConnectionClient interface { Recv() (*GuestMessage, error) grpc.ClientStream } type snapshotServiceCreateConnectionClient struct { grpc.ClientStream } func (x *snapshotServiceCreateConnectionClient) Recv() (*GuestMessage, error) { m := new(GuestMessage) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *snapshotServiceClient) HandleResponsesFromGuest(ctx context.Context, in *SnapshotResponse, opts ...grpc.CallOption) (*ServerAck, error) { out := new(ServerAck) err := c.cc.Invoke(ctx, "/cloud.vmm.SnapshotService/HandleResponsesFromGuest", in, out, opts...) if err != nil { return nil, err } return out, nil } // SnapshotServiceServer is the server API for SnapshotService service. type SnapshotServiceServer interface { // The client is expected to first send a "GuestReady" message, indicating // they are available and establishing the connection that vanadium writes // requests to the agent on. CreateConnection(*GuestReady, SnapshotService_CreateConnectionServer) error // Handles the agent's responses to the above requests. HandleResponsesFromGuest(context.Context, *SnapshotResponse) (*ServerAck, error) } // UnimplementedSnapshotServiceServer can be embedded to have forward compatible implementations. type UnimplementedSnapshotServiceServer struct { } func (*UnimplementedSnapshotServiceServer) CreateConnection(req *GuestReady, srv SnapshotService_CreateConnectionServer) error { return status.Errorf(codes.Unimplemented, "method CreateConnection not implemented") } func (*UnimplementedSnapshotServiceServer) HandleResponsesFromGuest(ctx context.Context, req *SnapshotResponse) (*ServerAck, error) { return nil, status.Errorf(codes.Unimplemented, "method HandleResponsesFromGuest not implemented") } func RegisterSnapshotServiceServer(s *grpc.Server, srv SnapshotServiceServer) { s.RegisterService(&_SnapshotService_serviceDesc, srv) } func _SnapshotService_CreateConnection_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(GuestReady) if err := stream.RecvMsg(m); err != nil { return err } return srv.(SnapshotServiceServer).CreateConnection(m, &snapshotServiceCreateConnectionServer{stream}) } type SnapshotService_CreateConnectionServer interface { Send(*GuestMessage) error grpc.ServerStream } type snapshotServiceCreateConnectionServer struct { grpc.ServerStream } func (x *snapshotServiceCreateConnectionServer) Send(m *GuestMessage) error { return x.ServerStream.SendMsg(m) } func _SnapshotService_HandleResponsesFromGuest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SnapshotResponse) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(SnapshotServiceServer).HandleResponsesFromGuest(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/cloud.vmm.SnapshotService/HandleResponsesFromGuest", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(SnapshotServiceServer).HandleResponsesFromGuest(ctx, req.(*SnapshotResponse)) } return interceptor(ctx, in, info, handler) } var _SnapshotService_serviceDesc = grpc.ServiceDesc{ ServiceName: "cloud.vmm.SnapshotService", HandlerType: (*SnapshotServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "HandleResponsesFromGuest", Handler: _SnapshotService_HandleResponsesFromGuest_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "CreateConnection", Handler: _SnapshotService_CreateConnection_Handler, ServerStreams: true, }, }, Metadata: "snapshot_service.proto", } guest-agent-20231004.02/google_guest_agent/sshca/000077500000000000000000000000001450737260700213675ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/sshca/sshca.go000066400000000000000000000070301450737260700230170ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package sshca is the actual writing end of the sshtrustedca pipeline. package sshca import ( "context" "encoding/json" "strings" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/events/sshtrustedca" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) // Certificates wrapps a list of certificate authorities. type Certificates struct { Certs []TrustedCert `json:"trustedCertificateAuthorities"` } // TrustedCert defines the object containing a public key. type TrustedCert struct { PublicKey string `json:"publicKey"` } var ( // cachedCertificate stores the previously retrieved certificate to be cached in case mds fails. cachedCertificate string // mdsClient is the metadata's client, used to query oslogin certificates. mdsClient *metadata.Client ) // Init initializes the sshca's event handler callback. func Init(eventManager *events.Manager) { mdsClient = metadata.New() eventManager.Subscribe(sshtrustedca.ReadEvent, nil, writeFile) } // writeFile is an event handler callback and writes the actual sshca content to the pipe // used by openssh to grant access based on ssh ca. func writeFile(ctx context.Context, evType string, data interface{}, evData *events.EventData) bool { // There was some error on the pipe watcher, just ignore it. if evData.Error != nil { logger.Debugf("Not handling ssh trusted ca cert event, we got an error: %+v", evData.Error) return true } // Make sure we close the pipe after we've done writing to it. pipeData := evData.Data.(*sshtrustedca.PipeData) defer func() { if err := pipeData.File.Close(); err != nil { logger.Errorf("Failed to close pipe: %+v", err) } pipeData.Finished() }() // The certificates key/endpoint is not cached, we can't rely on the metadata watcher data because of that. certificate, err := mdsClient.GetKey(ctx, "oslogin/certificates", nil) if err != nil && cachedCertificate != "" { certificate = cachedCertificate logger.Warningf("Failed to get certificate, assuming/using previously cached one.") } else if err != nil { logger.Errorf("Failed to get certificate from metadata server: %+v", err) return true } // Keep a copy of the returned certificate for error fallback caching. cachedCertificate = certificate var certs Certificates var outData []string if err := json.Unmarshal([]byte(certificate), &certs); err != nil { logger.Errorf("Failed to unmarshal certificate json: %+v", err) return true } for _, curr := range certs.Certs { outData = append(outData, curr.PublicKey) } outStr := strings.Join(outData, "\n") n, err := pipeData.File.WriteString(outStr) if err != nil { logger.Errorf("Failed to write certificate to the write end of the pipe: %+v", err) return true } if n != len(outStr) { logger.Errorf("Wrote the wrong ammout of data, wrote %d bytes instead of %d bytes", n, len(certificate)) } return true } guest-agent-20231004.02/google_guest_agent/stub.go000066400000000000000000000024131450737260700215720ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. //go:build !windows // +build !windows package main import ( "context" "errors" ) var errRegNotExist = errors.New("error") func resetPwd(username, pwd string) error { return nil } func readRegMultiString(key, name string) ([]string, error) { return nil, nil } func writeRegMultiString(key, name string, value []string) error { return nil } func deleteRegKey(key, name string) error { return nil } func getWindowsServiceImagePath(regKey string) (string, error) { return "", nil } func getWindowsExeVersion(ctx context.Context, path string) (versionInfo, error) { return versionInfo{0, 0}, nil } func checkWindowsServiceRunning(ctx context.Context, servicename string) bool { return false } guest-agent-20231004.02/google_guest_agent/system_windows.go000066400000000000000000000062701450737260700237200ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "context" "fmt" "strings" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/run" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "golang.org/x/sys/windows/registry" ) var errRegNotExist = registry.ErrNotExist var startRegKey = "Start" type ( DWORD uint32 LPWSTR *uint16 ) func init() { key, _, err := registry.CreateKey(registry.LOCAL_MACHINE, regKeyBase, registry.WRITE) if err != nil { logger.Fatalf(err.Error()) } key.Close() key, _, err = registry.CreateKey(registry.LOCAL_MACHINE, addressKey, registry.WRITE) if err != nil { logger.Fatalf(err.Error()) } key.Close() } func readRegMultiString(key, name string) ([]string, error) { k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE) if err != nil { return nil, err } defer k.Close() s, _, err := k.GetStringsValue(name) if err != nil { return nil, err } return s, nil } func readRegString(key, name string) (string, error) { k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE) if err != nil { return "", err } defer k.Close() s, _, err := k.GetStringValue(name) if err != nil { return "", err } return s, nil } func readRegInteger(key, name string) (uint64, error) { k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE) if err != nil { return 0, err } defer k.Close() i, _, err := k.GetIntegerValue(name) if err != nil { return 0, err } return i, nil } func writeRegMultiString(key, name string, value []string) error { k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.WRITE) if err != nil { return err } defer k.Close() return k.SetStringsValue(name, value) } func deleteRegKey(key, name string) error { k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.WRITE) if err != nil { return err } defer k.Close() return k.DeleteValue(name) } func checkWindowsServiceRunning(ctx context.Context, servicename string) bool { res := run.WithOutput(ctx, "sc", "query", servicename) return strings.Contains(res.StdOut, "RUNNING") } func getWindowsServiceImagePath(regKey string) (string, error) { regValue, err := readRegString(regKey, "ImagePath") if err != nil { return "", err } imagePath := strings.Trim(string(regValue), `"`) return imagePath, nil } func getWindowsExeVersion(ctx context.Context, path string) (versionInfo, error) { psCmd := "(Get-Item '" + path + "').VersionInfo.FileVersion" res := run.WithOutput(ctx, "powershell", "-c", psCmd) if res.ExitCode != 0 { return versionInfo{0, 0}, fmt.Errorf(res.Error()) } return parseVersionInfo([]byte(res.StdOut)) } guest-agent-20231004.02/google_guest_agent/telemetry/000077500000000000000000000000001450737260700223005ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/telemetry/proto/000077500000000000000000000000001450737260700234435ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/telemetry/proto/telemetry.pb.go000066400000000000000000000236061450737260700264130ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.21.12 // source: telemetry.proto package telemetry import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type OSInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields OsType *string `protobuf:"bytes,1,opt,name=os_type,json=osType,proto3,oneof" json:"os_type,omitempty"` LongName *string `protobuf:"bytes,2,opt,name=long_name,json=longName,proto3,oneof" json:"long_name,omitempty"` ShortName *string `protobuf:"bytes,3,opt,name=short_name,json=shortName,proto3,oneof" json:"short_name,omitempty"` Version *string `protobuf:"bytes,4,opt,name=version,proto3,oneof" json:"version,omitempty"` KernelVersion *string `protobuf:"bytes,5,opt,name=kernel_version,json=kernelVersion,proto3,oneof" json:"kernel_version,omitempty"` KernelRelease *string `protobuf:"bytes,6,opt,name=kernel_release,json=kernelRelease,proto3,oneof" json:"kernel_release,omitempty"` } func (x *OSInfo) Reset() { *x = OSInfo{} if protoimpl.UnsafeEnabled { mi := &file_telemetry_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OSInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*OSInfo) ProtoMessage() {} func (x *OSInfo) ProtoReflect() protoreflect.Message { mi := &file_telemetry_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OSInfo.ProtoReflect.Descriptor instead. func (*OSInfo) Descriptor() ([]byte, []int) { return file_telemetry_proto_rawDescGZIP(), []int{0} } func (x *OSInfo) GetOsType() string { if x != nil && x.OsType != nil { return *x.OsType } return "" } func (x *OSInfo) GetLongName() string { if x != nil && x.LongName != nil { return *x.LongName } return "" } func (x *OSInfo) GetShortName() string { if x != nil && x.ShortName != nil { return *x.ShortName } return "" } func (x *OSInfo) GetVersion() string { if x != nil && x.Version != nil { return *x.Version } return "" } func (x *OSInfo) GetKernelVersion() string { if x != nil && x.KernelVersion != nil { return *x.KernelVersion } return "" } func (x *OSInfo) GetKernelRelease() string { if x != nil && x.KernelRelease != nil { return *x.KernelRelease } return "" } type AgentInfo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Name *string `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"` Version *string `protobuf:"bytes,2,opt,name=version,proto3,oneof" json:"version,omitempty"` Architecture *string `protobuf:"bytes,3,opt,name=architecture,proto3,oneof" json:"architecture,omitempty"` } func (x *AgentInfo) Reset() { *x = AgentInfo{} if protoimpl.UnsafeEnabled { mi := &file_telemetry_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AgentInfo) String() string { return protoimpl.X.MessageStringOf(x) } func (*AgentInfo) ProtoMessage() {} func (x *AgentInfo) ProtoReflect() protoreflect.Message { mi := &file_telemetry_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AgentInfo.ProtoReflect.Descriptor instead. func (*AgentInfo) Descriptor() ([]byte, []int) { return file_telemetry_proto_rawDescGZIP(), []int{1} } func (x *AgentInfo) GetName() string { if x != nil && x.Name != nil { return *x.Name } return "" } func (x *AgentInfo) GetVersion() string { if x != nil && x.Version != nil { return *x.Version } return "" } func (x *AgentInfo) GetArchitecture() string { if x != nil && x.Architecture != nil { return *x.Architecture } return "" } var File_telemetry_proto protoreflect.FileDescriptor var file_telemetry_proto_rawDesc = []byte{ 0x0a, 0x0f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x22, 0xbe, 0x02, 0x0a, 0x06, 0x4f, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x07, 0x6f, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x6f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x6c, 0x6f, 0x6e, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2a, 0x0a, 0x0e, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x04, 0x52, 0x0d, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2a, 0x0a, 0x0e, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, 0x0d, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x6f, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x09, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x42, 0x1e, 0x5a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_telemetry_proto_rawDescOnce sync.Once file_telemetry_proto_rawDescData = file_telemetry_proto_rawDesc ) func file_telemetry_proto_rawDescGZIP() []byte { file_telemetry_proto_rawDescOnce.Do(func() { file_telemetry_proto_rawDescData = protoimpl.X.CompressGZIP(file_telemetry_proto_rawDescData) }) return file_telemetry_proto_rawDescData } var file_telemetry_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_telemetry_proto_goTypes = []interface{}{ (*OSInfo)(nil), // 0: telemetry.OSInfo (*AgentInfo)(nil), // 1: telemetry.AgentInfo } var file_telemetry_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_telemetry_proto_init() } func file_telemetry_proto_init() { if File_telemetry_proto != nil { return } if !protoimpl.UnsafeEnabled { file_telemetry_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OSInfo); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_telemetry_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AgentInfo); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_telemetry_proto_msgTypes[0].OneofWrappers = []interface{}{} file_telemetry_proto_msgTypes[1].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_telemetry_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_telemetry_proto_goTypes, DependencyIndexes: file_telemetry_proto_depIdxs, MessageInfos: file_telemetry_proto_msgTypes, }.Build() File_telemetry_proto = out.File file_telemetry_proto_rawDesc = nil file_telemetry_proto_goTypes = nil file_telemetry_proto_depIdxs = nil } guest-agent-20231004.02/google_guest_agent/telemetry/proto/telemetry.proto000066400000000000000000000017711450737260700265500ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. syntax = "proto3"; package telemetry; option go_package = "google_guest_agent/telemetry"; message OSInfo { optional string os_type = 1; optional string long_name = 2; optional string short_name = 3; optional string version = 4; optional string kernel_version = 5; optional string kernel_release = 6; } message AgentInfo { optional string name = 1; optional string version = 2; optional string architecture = 3; } guest-agent-20231004.02/google_guest_agent/telemetry/telemetry.go000066400000000000000000000102661450737260700246460ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package telemetry import ( "context" "encoding/base64" "runtime" "time" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "google.golang.org/protobuf/proto" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/osinfo" tpb "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/telemetry/proto" ) var ( telemetryJobID = "telemetryJobID" telemetryInterval = 24 * time.Hour ) // Data is telemetry data on the current agent and OS. type Data struct { // Name of the agent. AgentName string // Version of the Agent. AgentVersion string // Architecture of the Agent. AgentArch string // OS name. OS string // The name the OS uses to fully describe itself. LongName string // OS name in short form (aka distro name). ShortName string // Version of the OS. Version string // Kernel Release. KernelRelease string // Kernel Version. KernelVersion string } func formatGuestAgent(d Data) string { data, err := proto.Marshal(&tpb.AgentInfo{ Name: &d.AgentName, Version: &d.AgentVersion, Architecture: &d.AgentArch, }) if err != nil { logger.Warningf("Error marshalling AgentInfo: %v", err) } return base64.StdEncoding.EncodeToString(data) } func formatGuestOS(d Data) string { data, err := proto.Marshal(&tpb.OSInfo{ OsType: &d.OS, LongName: &d.LongName, ShortName: &d.ShortName, Version: &d.Version, KernelVersion: &d.KernelVersion, KernelRelease: &d.KernelRelease, }) if err != nil { logger.Warningf("Error marshalling AgentInfo: %v", err) } return base64.StdEncoding.EncodeToString(data) } // Record records telemetry data. func Record(ctx context.Context, client metadata.MDSClientInterface, d Data) error { headers := map[string]string{ "X-Google-Guest-Agent": formatGuestAgent(d), "X-Google-Guest-OS": formatGuestOS(d), } // This is the simplest metadata call we can make, and we dont care about any return value, // all we need to do is make some call with the telemetry headers. _, err := client.GetKey(ctx, "", headers) return err } // Job implements job scheduler interface for recording telemetry. type Job struct { client metadata.MDSClientInterface programName string agentVersion string } // New initializes a new TelemetryJob. func New(client metadata.MDSClientInterface, programName, agentVersion string) *Job { return &Job{ client: client, programName: programName, agentVersion: agentVersion, } } // ID returns the ID for this job. func (j *Job) ID() string { return telemetryJobID } // Run records telemetry data. func (j *Job) Run(ctx context.Context) (bool, error) { osInfo := osinfo.Get() d := Data{ AgentName: j.programName, AgentVersion: j.agentVersion, AgentArch: runtime.GOARCH, OS: runtime.GOOS, LongName: osInfo.PrettyName, ShortName: osInfo.OS, Version: osInfo.VersionID, KernelRelease: osInfo.KernelRelease, KernelVersion: osInfo.KernelVersion, } if err := Record(ctx, j.client, d); err != nil { // Log this here in Debug mode as telemetry is best effort. logger.Debugf("Error recording telemetry: %v", err) } return j.ShouldEnable(ctx), nil } // Interval returns the interval at which job is executed. func (j *Job) Interval() (time.Duration, bool) { return telemetryInterval, true } // ShouldEnable returns true as long as DisableTelemetry is not set in metadata. func (j *Job) ShouldEnable(ctx context.Context) bool { md, err := j.client.Get(ctx) if err != nil { return false } return !md.Instance.Attributes.DisableTelemetry && !md.Project.Attributes.DisableTelemetry } guest-agent-20231004.02/google_guest_agent/telemetry/telemetry_test.go000066400000000000000000000032071450737260700257020ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. package telemetry import ( "context" "testing" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/fakes" ) type mdsClient struct { getKeyHeaders map[string]string fakes.MDSClient } func (c *mdsClient) GetKey(ctx context.Context, key string, headers map[string]string) (string, error) { c.getKeyHeaders = headers return "", nil } func TestRecord(t *testing.T) { client := &mdsClient{} d := Data{ AgentVersion: "AgentVersion", AgentArch: "AgentArch", OS: "OS", LongName: "LongName", ShortName: "ShortName", Version: "Version", KernelRelease: "KernelRelease", KernelVersion: "KernelVersion", } if err := Record(context.Background(), client, d); err != nil { t.Fatalf("Error running Record: %v", err) } want := map[string]string{ "X-Google-Guest-Agent": formatGuestAgent(d), "X-Google-Guest-OS": formatGuestOS(d), } got := client.getKeyHeaders for k, v := range want { if got[k] != v { t.Errorf("received headers does not contain all expected headers, want: %q, got: %q", want, got) } } } guest-agent-20231004.02/google_guest_agent/uefi/000077500000000000000000000000001450737260700212165ustar00rootroot00000000000000guest-agent-20231004.02/google_guest_agent/uefi/uefi.go000066400000000000000000000020021450737260700224670ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package uefi provides utility functions to read UEFI variables. package uefi // VariableName represents UEFI variable name and GUID. // Format: {VariableName}-{VendorGUID} type VariableName struct { RootDir string Name string GUID string } // Variable represents UEFI Variable and its contents. // Attributes are not set in case of Windows. type Variable struct { Name VariableName Attributes []byte Content []byte } guest-agent-20231004.02/google_guest_agent/uefi/uefi_unix.go000066400000000000000000000030561450737260700235440ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. //go:build unix // Package uefi provides utility functions to read UEFI variables. package uefi import ( "fmt" "os" "path/filepath" ) const ( defaultEFIVarsDir = "/sys/firmware/efi/efivars" ) // Path returns a path for UEFI variable on disk. func (v VariableName) Path() string { root := v.RootDir if root == "" { root = defaultEFIVarsDir } return filepath.Join(root, v.Name+"-"+v.GUID) } // ReadVariable reads UEFI variable and returns as byte array. // Throws an error if variable is invalid or empty. func ReadVariable(v VariableName) (*Variable, error) { path := v.Path() b, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("error reading %q: %v", path, err) } // According to UEFI specification the first four bytes of the contents are attributes. if len(b) < 4 { return nil, fmt.Errorf("%q contains %d bytes of data, it should have at least 4", path, len(b)) } return &Variable{ Name: v, Attributes: b[:4], Content: b[4:], }, nil } guest-agent-20231004.02/google_guest_agent/uefi/uefi_unix_test.go000066400000000000000000000043111450737260700245760ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. //go:build unix package uefi import ( "os" "path/filepath" "testing" ) func TestVariablePath(t *testing.T) { v := VariableName{Name: "name", GUID: "guid"} want := "/sys/firmware/efi/efivars/name-guid" if got := v.Path(); got != want { t.Errorf("VariablePath(%+v) = %v, want %v", v, got, want) } } func TestReadVariable(t *testing.T) { root := t.TempDir() v := VariableName{Name: "testname", GUID: "testguid", RootDir: root} fakecert := ` -----BEGIN CERTIFICATE----- sdfsd -----END CERTIFICATE----- ` fakeUefi := []byte("attr" + fakecert) path := filepath.Join(root, "testname-testguid") if err := os.WriteFile(path, fakeUefi, 0644); err != nil { t.Fatalf("Failed to write test file: %v", err) } defer os.Remove(path) got, err := ReadVariable(v) if err != nil { t.Errorf("ReadVariable(%+v) failed unexpectedly with error: %v", v, err) } if string(got.Attributes) != "attr" { t.Errorf("ReadVariable(%+v) = %s as attributes, want %s", v, string(got.Attributes), "attr") } if string(got.Content) != fakecert { t.Errorf("ReadVariable(%+v) = %s as content, want %s", v, string(got.Content), fakeUefi) } } func TestReadVariableError(t *testing.T) { root := t.TempDir() v := VariableName{Name: "testname", GUID: "testguid", RootDir: root} p := filepath.Join(root, "testname-testguid") // File not exist error. _, err := ReadVariable(v) if err == nil { t.Errorf("ReadVariable(%+v) succeeded for non-existent file, want error", v) } // Empty variable error. os.WriteFile(p, []byte(""), 0644) _, err = ReadVariable(v) if err == nil { t.Errorf("ReadVariable(%+v) succeeded for invalid format, want error", v) } } guest-agent-20231004.02/google_guest_agent/uefi/uefi_windows.go000066400000000000000000000152571450737260700242610ustar00rootroot00000000000000// Copyright 2023 Google LLC // 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 // https://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. // Package uefi provides utility functions to read UEFI variables. package uefi import ( "fmt" "syscall" "unsafe" "golang.org/x/sys/windows" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) var ( // https://en.wikipedia.org/wiki/Microsoft_Windows_library_files#KERNEL32.DLL kernelDLL = windows.NewLazySystemDLL("kernel32.dll") // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess // procGetCurrentProcess retrieves a pseudo handle for the current process. procGetCurrentProcess = kernelDLL.NewProc("GetCurrentProcess") // https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle // procCloseHandle closes an open process object handle. procCloseHandle = kernelDLL.NewProc("CloseHandle") // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getfirmwareenvironmentvariablew // procGetFirmwareEnvironmentVariableW retrieves the value of the specified UEFI. procGetFirmwareEnvironmentVariableW = kernelDLL.NewProc("GetFirmwareEnvironmentVariableW") // https://en.wikipedia.org/wiki/Microsoft_Windows_library_files#ADVAPI32.DLL advapiDLL = windows.NewLazySystemDLL("advapi32.dll") // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken // procOpenProcessToken opens the access token (contains the security information for a logon session) associated for a process. // Token identifies the user, the user's groups, and the user's privileges. procOpenProcessToken = advapiDLL.NewProc("OpenProcessToken") // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupprivilegevaluew // procLookupPrivilegeValueW is used to retrieve the locally unique identifier (LUID) procLookupPrivilegeValueW = advapiDLL.NewProc("LookupPrivilegeValueW") // https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges // procAdjustTokenPrivileges is used for enabling the privileges on the access token. procAdjustTokenPrivileges = advapiDLL.NewProc("AdjustTokenPrivileges") ) const ( // SE_SYSTEM_ENVIRONMENT_NAME is the privilege required to read a firmware environment variable. SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege" // PROC_TOKEN_ADJUST_PRIVILEGES is access required to change the specified privileges. PROC_TOKEN_ADJUST_PRIVILEGES = 0x0020 // PROC_SE_PRIVILEGE_ENABLED is privilege attribute used with LUID_AND_ATTRIBUTES stating to // enable the specified privilege. PROC_SE_PRIVILEGE_ENABLED = 0x00000002 ) // https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-luid // LUID is the opaque identifier structure that is guaranteed to be unique on the local machine. // It is used to locally represent the privilege name (e.g. SeSystemEnvironmentPrivilege in this case). type LUID struct { LowPart uint32 HighPart int32 } // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-luid_and_attributes // LUID_AND_ATTRIBUTES structure represents a locally unique identifier (LUID) and its attributes. type LUID_AND_ATTRIBUTES struct { LUID LUID Attributes uint32 } // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-token_privileges // TOKEN_PRIVILEGES is the structure that contains information about a set of privileges for an access token. type TOKEN_PRIVILEGES struct { PrivilegeCount uint32 Privileges [1]LUID_AND_ATTRIBUTES } // ReadVariable reads UEFI variable and returns as byte array. // Throws an error if variable is invalid or empty. func ReadVariable(v VariableName) (*Variable, error) { logger.Debugf("Enabling required %s priviliges for agent process", SE_SYSTEM_ENVIRONMENT_NAME) if err := enablePrivilege(SE_SYSTEM_ENVIRONMENT_NAME); err != nil { return nil, err } name := unsafe.Pointer(syscall.StringToUTF16Ptr(v.Name)) guid := unsafe.Pointer(syscall.StringToUTF16Ptr("{" + v.GUID + "}")) buffer := make([]byte, 1024) // This call returns number of bytes written to the output buffer, unused, error size, _, err := procGetFirmwareEnvironmentVariableW.Call( uintptr(name), uintptr(guid), uintptr(unsafe.Pointer(&buffer[0])), uintptr(uint32(len(buffer))), ) if size == uintptr(0) { return nil, fmt.Errorf("unable to read UEFI variable %+v: %w", v, err) } return &Variable{ Name: v, Attributes: []byte{}, Content: buffer[:size], }, nil } // enablePrivilege enables the specified privilege for current process. func enablePrivilege(name string) error { // Get current process handle. handle, _, err := procGetCurrentProcess.Call() if handle == uintptr(0) { return fmt.Errorf("unable to get current process handle: %w", err) } defer procCloseHandle.Call(handle) // Get access token that contains the privileges to be modified for the current process. var tHandle uintptr opRes, _, err := procOpenProcessToken.Call( uintptr(handle), uintptr(uint32(PROC_TOKEN_ADJUST_PRIVILEGES)), uintptr(unsafe.Pointer(&tHandle)), ) if opRes == uintptr(0) { return fmt.Errorf("unable to open current process token: %w", err) } defer procCloseHandle.Call(tHandle) // Generate a pointer to a null-terminated string that specifies the name of the privilege. namePtr, err := syscall.UTF16PtrFromString(name) if err != nil { return fmt.Errorf("unable to encode privilege name(%s) to UTF16: %w", name, err) } // Retrieve the LUID for the required privilege. var luid LUID lpRes, _, err := procLookupPrivilegeValueW.Call( uintptr(0), uintptr(unsafe.Pointer(namePtr)), uintptr(unsafe.Pointer(&luid)), ) if lpRes == uintptr(0) { return fmt.Errorf("unable to lookup LUID for privilege %q: %w", name, err) } newState := TOKEN_PRIVILEGES{PrivilegeCount: 1} newState.Privileges[0] = LUID_AND_ATTRIBUTES{ LUID: luid, Attributes: PROC_SE_PRIVILEGE_ENABLED, } // Enable specified privilege on the current process. ajRes, _, err := procAdjustTokenPrivileges.Call( uintptr(tHandle), uintptr(uint32(0)), uintptr(unsafe.Pointer(&newState)), uintptr(uint32(0)), uintptr(0), uintptr(0), ) if ajRes == uintptr(0) { return fmt.Errorf("unable to set privilege %q: %w", name, err) } return nil } guest-agent-20231004.02/google_guest_agent/windows_accounts.go000066400000000000000000000277571450737260700242300ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "bytes" "context" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/json" "fmt" "hash" "math/big" "reflect" "runtime" "strconv" "strings" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) var ( accountRegKey = "PublicKeys" credsWriter = &utils.SerialPort{Port: "COM4"} minSSHVersion = versionInfo{8, 6} sshdRegKey = `SYSTEM\CurrentControlSet\Services\sshd` ) // newPwd will generate a random password that meets Windows complexity // requirements: https://technet.microsoft.com/en-us/library/cc786468. // Characters that are difficult for users to type on a command line (quotes, // non english characters) are not used. func newPwd(userPwLgth int) (string, error) { var pwLgth int minPwLgth := 15 maxPwLgth := 255 lower := []byte("abcdefghijklmnopqrstuvwxyz") upper := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ") numbers := []byte("0123456789") special := []byte(`~!@#$%^&*_-+=|\(){}[]:;<>,.?/`) chars := bytes.Join([][]byte{lower, upper, numbers, special}, nil) pwLgth = minPwLgth if userPwLgth > minPwLgth { pwLgth = userPwLgth } if userPwLgth > maxPwLgth { pwLgth = maxPwLgth } for { b := make([]byte, pwLgth) for i := range b { ci, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) if err != nil { return "", err } b[i] = chars[ci.Int64()] } var l, u, n, s int if bytes.ContainsAny(lower, string(b)) { l = 1 } if bytes.ContainsAny(upper, string(b)) { u = 1 } if bytes.ContainsAny(numbers, string(b)) { n = 1 } if bytes.ContainsAny(special, string(b)) { s = 1 } // If the password does not meet Windows complexity requirements, try again. // https://technet.microsoft.com/en-us/library/cc786468 if l+u+n+s >= 3 { return string(b), nil } } } type credsJSON struct { ErrorMessage string `json:"errorMessage,omitempty"` EncryptedPassword string `json:"encryptedPassword,omitempty"` UserName string `json:"userName,omitempty"` PasswordFound bool `json:"passwordFound,omitempty"` Exponent string `json:"exponent,omitempty"` Modulus string `json:"modulus,omitempty"` HashFunction string `json:"hashFunction,omitempty"` } func printCreds(creds *credsJSON) error { data, err := json.Marshal(creds) if err != nil { return err } _, err = credsWriter.Write(append(data, []byte("\n")...)) return err } func createOrResetPwd(ctx context.Context, k metadata.WindowsKey) (*credsJSON, error) { pwd, err := newPwd(k.PasswordLength) if err != nil { return nil, fmt.Errorf("error creating password: %v", err) } if _, err := userExists(k.UserName); err == nil { logger.Infof("Resetting password for user %s", k.UserName) if err := resetPwd(k.UserName, pwd); err != nil { return nil, fmt.Errorf("error running resetPwd: %v", err) } if k.AddToAdministrators != nil && *k.AddToAdministrators { if err := addUserToGroup(ctx, k.UserName, "Administrators"); err != nil { return nil, fmt.Errorf("error running addUserToGroup: %v", err) } } } else { logger.Infof("Creating user %s", k.UserName) if err := createUser(ctx, k.UserName, pwd); err != nil { return nil, fmt.Errorf("error running createUser: %v", err) } if k.AddToAdministrators == nil || *k.AddToAdministrators { if err := addUserToGroup(ctx, k.UserName, "Administrators"); err != nil { return nil, fmt.Errorf("error running addUserToGroup: %v", err) } } } return createcredsJSON(k, pwd) } func createSSHUser(ctx context.Context, user string) error { pwd, err := newPwd(20) if err != nil { return fmt.Errorf("error creating password: %v", err) } if _, err := userExists(user); err == nil { return nil } logger.Infof("Creating user %s", user) if err := createUser(ctx, user, pwd); err != nil { return fmt.Errorf("error running createUser: %v", err) } if err := addUserToGroup(ctx, user, "Administrators"); err != nil { return fmt.Errorf("error running addUserToGroup: %v", err) } return nil } func createcredsJSON(k metadata.WindowsKey, pwd string) (*credsJSON, error) { mod, err := base64.StdEncoding.DecodeString(k.Modulus) if err != nil { return nil, fmt.Errorf("error decoding modulus: %v", err) } exp, err := base64.StdEncoding.DecodeString(k.Exponent) if err != nil { return nil, fmt.Errorf("error decoding exponent: %v", err) } key := &rsa.PublicKey{ N: new(big.Int).SetBytes(mod), E: int(new(big.Int).SetBytes(exp).Int64()), } if k.HashFunction == "" { k.HashFunction = "sha1" } var hashFunc hash.Hash switch k.HashFunction { case "sha1": hashFunc = sha1.New() case "sha256": hashFunc = sha256.New() case "sha512": hashFunc = sha512.New() default: return nil, fmt.Errorf("unknown hash function requested: %q", k.HashFunction) } encPwd, err := rsa.EncryptOAEP(hashFunc, rand.Reader, key, []byte(pwd), nil) if err != nil { return nil, fmt.Errorf("error encrypting password: %v", err) } return &credsJSON{ PasswordFound: true, Exponent: k.Exponent, Modulus: k.Modulus, UserName: k.UserName, HashFunction: k.HashFunction, EncryptedPassword: base64.StdEncoding.EncodeToString(encPwd), }, nil } func getWinSSHEnabled(md *metadata.Descriptor) bool { var enable bool if md.Project.Attributes.EnableWindowsSSH != nil { enable = *md.Project.Attributes.EnableWindowsSSH } if md.Instance.Attributes.EnableWindowsSSH != nil { enable = *md.Instance.Attributes.EnableWindowsSSH } return enable } type winAccountsMgr struct { // fakeWindows forces Disabled to run as if it was running in a windows system. // mostly target for unit tests. fakeWindows bool } func (a *winAccountsMgr) Diff(ctx context.Context) (bool, error) { oldSSHEnable := getWinSSHEnabled(oldMetadata) sshEnable := getWinSSHEnabled(newMetadata) if sshEnable != oldSSHEnable { return true, nil } if !reflect.DeepEqual(newMetadata.Instance.Attributes.WindowsKeys, oldMetadata.Instance.Attributes.WindowsKeys) { return true, nil } if !compareStringSlice(newMetadata.Instance.Attributes.SSHKeys, oldMetadata.Instance.Attributes.SSHKeys) { return true, nil } if !compareStringSlice(newMetadata.Project.Attributes.SSHKeys, oldMetadata.Project.Attributes.SSHKeys) { return true, nil } if newMetadata.Instance.Attributes.BlockProjectKeys != oldMetadata.Instance.Attributes.BlockProjectKeys { return true, nil } return false, nil } func (a *winAccountsMgr) Timeout(ctx context.Context) (bool, error) { return false, nil } func (a *winAccountsMgr) Disabled(ctx context.Context) (bool, error) { if !a.fakeWindows && runtime.GOOS != "windows" { return true, nil } config := cfg.Get() if config.AccountManager != nil { return config.AccountManager.Disable, nil } if newMetadata.Instance.Attributes.DisableAccountManager != nil { return *newMetadata.Instance.Attributes.DisableAccountManager, nil } if newMetadata.Project.Attributes.DisableAccountManager != nil { return *newMetadata.Project.Attributes.DisableAccountManager, nil } return false, nil } type versionInfo struct { major int minor int } func (v versionInfo) String() string { return fmt.Sprintf("%d.%d", v.major, v.minor) } func parseVersionInfo(psOutput []byte) (versionInfo, error) { verInfo := versionInfo{0, 0} verStr := strings.TrimSpace(string(psOutput)) splitVer := strings.Split(verStr, ".") if len(splitVer) < 2 { return verInfo, fmt.Errorf("cannot parse OpenSSH version string: %v", verStr) } majorVer, err := strconv.Atoi(splitVer[0]) if err != nil { return verInfo, err } verInfo.major = majorVer minorVer, err := strconv.Atoi(splitVer[1]) if err != nil { return verInfo, err } verInfo.minor = minorVer return verInfo, nil } func versionOk(checkVersion versionInfo, minVersion versionInfo) error { versionError := fmt.Errorf("detected OpenSSH version may be incompatible with enable_windows_ssh. Found version %s, Need Version: %s", checkVersion, minVersion) if checkVersion.major < minVersion.major { return versionError } if checkVersion.major == minVersion.major && checkVersion.minor < minVersion.minor { return versionError } return nil } func verifyWinSSHVersion(ctx context.Context) error { sshdPath, err := getWindowsServiceImagePath(sshdRegKey) if err != nil { return fmt.Errorf("cannot determine sshd path: %v", err) } sshdVersion, err := getWindowsExeVersion(ctx, sshdPath) if err != nil { return fmt.Errorf("cannot determine OpenSSH Version: %v", err) } return versionOk(sshdVersion, minSSHVersion) } func (a *winAccountsMgr) Set(ctx context.Context) error { oldSSHEnable := getWinSSHEnabled(oldMetadata) sshEnable := getWinSSHEnabled(newMetadata) if sshEnable { if sshEnable != oldSSHEnable { err := verifyWinSSHVersion(ctx) if err != nil { logger.Warningf(err.Error()) } if !checkWindowsServiceRunning(ctx, "sshd") { logger.Warningf("The 'enable-windows-ssh' metadata key is set to 'true' " + "but sshd does not appear to be running.") } } if sshKeys == nil { logger.Debugf("initialize sshKeys map") sshKeys = make(map[string][]string) } mdkeys := newMetadata.Instance.Attributes.SSHKeys if !newMetadata.Instance.Attributes.BlockProjectKeys { mdkeys = append(mdkeys, newMetadata.Project.Attributes.SSHKeys...) } mdKeyMap := getUserKeys(mdkeys) for user := range mdKeyMap { if err := createSSHUser(ctx, user); err != nil { logger.Errorf("Error creating user: %s", err) } } } newKeys := newMetadata.Instance.Attributes.WindowsKeys regKeys, err := readRegMultiString(regKeyBase, accountRegKey) if err != nil && err != errRegNotExist { return err } toAdd := compareAccounts(newKeys, regKeys) for _, key := range toAdd { creds, err := createOrResetPwd(ctx, key) if err == nil { printCreds(creds) continue } logger.Errorf("error setting password: %s", err) creds = &credsJSON{ PasswordFound: false, Exponent: key.Exponent, Modulus: key.Modulus, UserName: key.UserName, ErrorMessage: err.Error(), } printCreds(creds) } var jsonKeys []string for _, key := range newKeys { jsn, err := json.Marshal(key) if err != nil { // This *should* never happen as each key was just Unmarshalled above. logger.Errorf("Failed to marshal windows key to JSON: %s", err) continue } jsonKeys = append(jsonKeys, string(jsn)) } return writeRegMultiString(regKeyBase, accountRegKey, jsonKeys) } var badReg []string func compareAccounts(newKeys metadata.WindowsKeys, oldStrKeys []string) metadata.WindowsKeys { if len(newKeys) == 0 { return nil } if len(oldStrKeys) == 0 { return newKeys } var oldKeys metadata.WindowsKeys for _, s := range oldStrKeys { var key metadata.WindowsKey if err := json.Unmarshal([]byte(s), &key); err != nil { if !utils.ContainsString(s, badReg) { logger.Errorf("Bad windows key from registry: %s", err) badReg = append(badReg, s) } continue } oldKeys = append(oldKeys, key) } var toAdd metadata.WindowsKeys for _, key := range newKeys { if func(key metadata.WindowsKey, oldKeys metadata.WindowsKeys) bool { for _, oldKey := range oldKeys { if oldKey.UserName == key.UserName && oldKey.Modulus == key.Modulus && oldKey.ExpireOn == key.ExpireOn { return false } } return true }(key, oldKeys) { toAdd = append(toAdd, key) } } return toAdd } guest-agent-20231004.02/google_guest_agent/windows_accounts_test.go000066400000000000000000000316641450737260700252570ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "context" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "hash" "math/big" "reflect" "testing" "time" "unicode" "github.com/GoogleCloudPlatform/guest-agent/metadata" "github.com/GoogleCloudPlatform/guest-agent/utils" ) func mkptr(b bool) *bool { ret := b return &ret } func TestExpired(t *testing.T) { var tests = []struct { sTime string e bool }{ {time.Now().Add(5 * time.Minute).Format(time.RFC3339), false}, {time.Now().Add(-5 * time.Minute).Format(time.RFC3339), true}, {"some bad time", true}, } for _, tt := range tests { k := metadata.WindowsKey{ExpireOn: tt.sTime} expired, _ := utils.CheckExpired(k.ExpireOn) if tt.e != expired { t.Errorf("windowsKey.expired() with ExpiredOn %q should return %t", k.ExpireOn, tt.e) } } } func TestAccountsDisabled(t *testing.T) { var tests = []struct { name string data []byte md *metadata.Descriptor want bool }{ {"not explicitly disabled", []byte(""), &metadata.Descriptor{}, false}, {"enabled in cfg only", []byte("[accountManager]\ndisable=false"), &metadata.Descriptor{}, false}, {"disabled in cfg only", []byte("[accountManager]\ndisable=true"), &metadata.Descriptor{}, true}, {"disabled in cfg, enabled in instance metadata", []byte("[accountManager]\ndisable=true"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAccountManager: mkptr(false)}}}, true}, {"enabled in cfg, disabled in instance metadata", []byte("[accountManager]\ndisable=false"), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAccountManager: mkptr(true)}}}, false}, {"enabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAccountManager: mkptr(false)}}}, false}, {"enabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{DisableAccountManager: mkptr(false)}}}, false}, {"disabled in instance metadata only", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAccountManager: mkptr(true)}}}, true}, {"enabled in instance metadata, disabled in project metadata", []byte(""), &metadata.Descriptor{Instance: metadata.Instance{Attributes: metadata.Attributes{DisableAccountManager: mkptr(false)}}, Project: metadata.Project{Attributes: metadata.Attributes{DisableAccountManager: mkptr(true)}}}, false}, {"disabled in project metadata only", []byte(""), &metadata.Descriptor{Project: metadata.Project{Attributes: metadata.Attributes{DisableAccountManager: mkptr(true)}}}, true}, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reloadConfig(t, tt.data) newMetadata = tt.md mgr := &winAccountsMgr{ fakeWindows: true, } got, err := mgr.Disabled(ctx) if err != nil { t.Errorf("Failed to run winAccountsMgr's Disabled() call: %+v", err) } if got != tt.want { t.Errorf("accounts.disabled() got: %t, want: %t", got, tt.want) } }) } reloadConfig(t, nil) got, err := (&winAccountsMgr{}).Disabled(ctx) if err != nil { t.Errorf("Failed to run winAccountsMgr's Disabled() call: %+v", err) } if got != true { t.Errorf("winAccountsMgr.disabled(\"linux\") got: %t, want: true", got) } } // rename this with leading disabled because this is a resource // intensive test. this test takes approx. 141 seconds to complete, next // longest test is 0.43 seconds. func disabledTestNewPwd(t *testing.T) { minPasswordLength := 15 maxPasswordLength := 255 var tests = []struct { name string passwordLength int wantPasswordLength int }{ {"0 characters, default value", 0, minPasswordLength}, {"5 characters, below min", 5, minPasswordLength}, {"15 characters", 5, minPasswordLength}, {"30 characters", 30, 30}, {"127 characters", 127, 127}, {"254 characters", 254, 254}, {"256 characters", 256, maxPasswordLength}, } for _, tt := range tests { for i := 0; i < 100000; i++ { pwd, err := newPwd(tt.passwordLength) if err != nil { t.Fatal(err) } if len(pwd) != tt.wantPasswordLength { t.Errorf("Password is not %d characters: len(%s)=%d", tt.wantPasswordLength, pwd, len(pwd)) } var l, u, n, s int for _, r := range pwd { switch { case unicode.IsLower(r): l = 1 case unicode.IsUpper(r): u = 1 case unicode.IsDigit(r): n = 1 case unicode.IsPunct(r) || unicode.IsSymbol(r): s = 1 } } if l+u+n+s < 3 { t.Errorf("Password does not have at least one character from 3 categories: '%v'", pwd) } } } } func TestCreatecredsJSON(t *testing.T) { pwd := "password" prv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatalf("error generating key: %v", err) } k := metadata.WindowsKey{ Email: "email", ExpireOn: "expire", Exponent: base64.StdEncoding.EncodeToString(new(big.Int).SetInt64(int64(prv.PublicKey.E)).Bytes()), Modulus: base64.StdEncoding.EncodeToString(prv.PublicKey.N.Bytes()), UserName: "username", } for name, hashFunc := range map[string]hash.Hash{"": sha1.New(), "sha1": sha1.New(), "sha256": sha256.New(), "sha512": sha512.New()} { k.HashFunction = name c, err := createcredsJSON(k, pwd) if err != nil { t.Fatalf("error running createcredsJSON: %v", err) } if k.HashFunction == "" { k.HashFunction = "sha1" } bPwd, err := base64.StdEncoding.DecodeString(c.EncryptedPassword) if err != nil { t.Fatalf("error base64 decoding encoded pwd: %v", err) } decPwd, err := rsa.DecryptOAEP(hashFunc, rand.Reader, prv, bPwd, nil) if err != nil { t.Fatalf("error decrypting password: %v", err) } if pwd != string(decPwd) { t.Errorf("decrypted password does not match expected for hash func %q, got: %s, want: %s", name, string(decPwd), pwd) } if k.UserName != c.UserName { t.Errorf("returned credsJSON UserName field unexpected, got: %s, want: %s", c.UserName, k.UserName) } if k.HashFunction != c.HashFunction { t.Errorf("returned credsJSON HashFunction field unexpected, got: %s, want: %s", c.HashFunction, k.HashFunction) } if !c.PasswordFound { t.Error("returned credsJSON PasswordFound field is not true") } } } func TestCompareAccounts(t *testing.T) { var tests = []struct { newKeys metadata.WindowsKeys oldStrKeys []string wantAdd metadata.WindowsKeys }{ // These should return toAdd: // In MD, not Reg {metadata.WindowsKeys{{UserName: "foo"}}, nil, metadata.WindowsKeys{{UserName: "foo"}}}, {metadata.WindowsKeys{{UserName: "foo"}}, []string{`{"UserName":"bar"}`}, metadata.WindowsKeys{{UserName: "foo"}}}, // These should return nothing: // In Reg and MD {metadata.WindowsKeys{{UserName: "foo"}}, []string{`{"UserName":"foo"}`}, nil}, // In Reg, not MD {nil, []string{`{UserName":"foo"}`}, nil}, } for _, tt := range tests { toAdd := compareAccounts(tt.newKeys, tt.oldStrKeys) if !reflect.DeepEqual(tt.wantAdd, toAdd) { t.Errorf("toAdd does not match expected: newKeys: %v, oldStrKeys: %q, got: %v, want: %v", tt.newKeys, tt.oldStrKeys, toAdd, tt.wantAdd) } } } func TestGetUserKeys(t *testing.T) { var tests = []struct { key string expectedValid int }{ {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0000"}`, 1, }, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700"}`, 1, }, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700", "futureField": "UNUSED_FIELDS_IGNORED"}`, 1, }, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0000"}`, 0, }, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0700"}`, 0, }, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"INVALID_TIMESTAMP"}`, 0, }, {`user:ssh-rsa [KEY] google-ssh`, 0, }, {`user:ssh-rsa [KEY] user`, 1, }, {`user:ssh-rsa [KEY]`, 1, }, {`malformed-ssh-keys [KEY] google-ssh`, 0, }, {`:malformed-ssh-keys [KEY] google-ssh`, 0, }, } for _, tt := range tests { ret := getUserKeys([]string{tt.key}) if userKeys := ret["user"]; len(userKeys) != tt.expectedValid { t.Errorf("expected %d valid keys from getUserKeys, but %d", tt.expectedValid, len(userKeys)) } } } func TestRemoveExpiredKeys(t *testing.T) { var tests = []struct { key string valid bool }{ {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0000"}`, true}, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700"}`, true}, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700", "futureField": "UNUSED_FIELDS_IGNORED"}`, true}, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0000"}`, false}, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0700"}`, false}, {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"INVALID_TIMESTAMP"}`, false}, {`user:ssh-rsa [KEY] google-ssh`, false}, {`user:ssh-rsa [KEY] user`, true}, {`user:ssh-rsa [KEY]`, true}, // having the user: prefix should not affect whether a key is expired, repeat test cases without user: prefix {`ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0000"}`, true}, {`ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700"}`, true}, {`ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700", "futureField": "UNUSED_FIELDS_IGNORED"}`, true}, {`ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0000"}`, false}, {`ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0700"}`, false}, {`ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"INVALID_TIMESTAMP"}`, false}, {`ssh-rsa [KEY] google-ssh`, false}, {`ssh-rsa [KEY] user`, true}, {`ssh-rsa [KEY]`, true}, {}, } for _, tt := range tests { ret := removeExpiredKeys([]string{tt.key}) if tt.valid { if len(ret) == 0 || ret[0] != tt.key { t.Errorf("valid key was removed: %q", tt.key) } } if !tt.valid && len(ret) == 1 { t.Errorf("invalid key was kept: %q", tt.key) } } } func TestVersionOk(t *testing.T) { tests := []struct { version versionInfo minVersion versionInfo hasErr bool }{ { version: versionInfo{8, 6}, minVersion: versionInfo{8, 6}, hasErr: false, }, { version: versionInfo{9, 3}, minVersion: versionInfo{8, 6}, hasErr: false, }, { version: versionInfo{8, 3}, minVersion: versionInfo{8, 6}, hasErr: true, }, { version: versionInfo{7, 9}, minVersion: versionInfo{8, 6}, hasErr: true, }, } for _, tt := range tests { err := versionOk(tt.version, tt.minVersion) hasErr := err != nil if hasErr != tt.hasErr { t.Errorf("versionOk error not correct: Got: %v, Want: %v for Version %d.%d with Min Version of %d.%d", hasErr, tt.hasErr, tt.version.major, tt.version.minor, tt.minVersion.major, tt.minVersion.minor) } } } func TestParseVersionInfo(t *testing.T) { tests := []struct { psOutput []byte expectedVer versionInfo expectErr bool }{ { psOutput: []byte("8.6.0.0\r\n"), expectedVer: versionInfo{8, 6}, expectErr: false, }, { psOutput: []byte("8.6.0.0"), expectedVer: versionInfo{8, 6}, expectErr: false, }, { psOutput: []byte("8.6\r\n"), expectedVer: versionInfo{8, 6}, expectErr: false, }, { psOutput: []byte("12345.34567.34566.3463456\r\n"), expectedVer: versionInfo{12345, 34567}, expectErr: false, }, { psOutput: []byte("8\r\n"), expectedVer: versionInfo{0, 0}, expectErr: true, }, { psOutput: []byte("\r\n"), expectedVer: versionInfo{0, 0}, expectErr: true, }, } for _, tt := range tests { verInfo, err := parseVersionInfo(tt.psOutput) hasErr := err != nil if verInfo != tt.expectedVer || hasErr != tt.expectErr { t.Errorf("parseVersionInfo(%v) not correct: Got: %v, Error: %v, Want: %v, Error: %v", tt.psOutput, verInfo, hasErr, tt.expectedVer, tt.expectErr) } } } guest-agent-20231004.02/google_guest_agent/wsfc.go000066400000000000000000000160751450737260700215700ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "context" "net" "strings" "sync" "time" "github.com/GoogleCloudPlatform/guest-agent/google_guest_agent/cfg" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) const wsfcDefaultAgentPort = "59998" type agentState int // Enum for agentState const ( running agentState = iota stopped ) var ( once sync.Once agentInstance *wsfcAgent ) type wsfcManager struct { agentNewState agentState agentNewPort string agent healthAgent } // Create new wsfcManager based on metadata agent request state will be set to // running if one of the following is true: // - EnableWSFC is set // - WSFCAddresses is set (As an advanced setting, it will always override EnableWSFC flag) func newWsfcManager() *wsfcManager { newState := stopped config := cfg.Get() if func() bool { if config.WSFC != nil && config.WSFC.Enable && config.WSFC.Addresses != "" { return config.WSFC.Enable } if newMetadata.Instance.Attributes.EnableWSFC != nil { return *newMetadata.Instance.Attributes.EnableWSFC } if newMetadata.Instance.Attributes.WSFCAddresses != "" { return true } if newMetadata.Project.Attributes.EnableWSFC != nil { return *newMetadata.Project.Attributes.EnableWSFC } if newMetadata.Project.Attributes.WSFCAddresses != "" { return true } return false }() { newState = running } newPort := wsfcDefaultAgentPort if config.WSFC != nil && config.WSFC.Port != "" { newPort = config.WSFC.Port } else if newMetadata.Instance.Attributes.WSFCAgentPort != "" { newPort = newMetadata.Instance.Attributes.WSFCAgentPort } else if newMetadata.Project.Attributes.WSFCAgentPort != "" { newPort = newMetadata.Instance.Attributes.WSFCAgentPort } return &wsfcManager{agentNewState: newState, agentNewPort: newPort, agent: getWsfcAgentInstance()} } // Implement manager.diff() func (m *wsfcManager) Diff(ctx context.Context) (bool, error) { return m.agentNewState != m.agent.getState() || m.agentNewPort != m.agent.getPort(), nil } // Implement manager.disabled(). // wsfc manager is always enabled. The manager is just a broker which manages the state of wsfcAgent. User // can disable the wsfc feature by setting the metadata. If the manager is disabled, the agent will stop. func (m *wsfcManager) Disabled(ctx context.Context) (bool, error) { return false, nil } func (m *wsfcManager) Timeout(ctx context.Context) (bool, error) { return false, nil } // Diff will always be called before set. So in set, only two cases are possible: // - state changed: start or stop the wsfc agent accordingly // - port changed: restart the agent if it is running func (m *wsfcManager) Set(ctx context.Context) error { m.agent.setPort(m.agentNewPort) // if state changes if m.agentNewState != m.agent.getState() { if m.agentNewState == running { return m.agent.run() } return m.agent.stop() } // If port changed if m.agent.getState() == running { if err := m.agent.stop(); err != nil { return err } return m.agent.run() } return nil } // interface for agent answering health check ping type healthAgent interface { getState() agentState getPort() string setPort(string) run() error stop() error } // Windows failover cluster agent, implements healthAgent interface type wsfcAgent struct { port string waitGroup *sync.WaitGroup listener *net.TCPListener } // Start agent and taking tcp request func (a *wsfcAgent) run() error { if a.getState() == running { logger.Infof("wsfc agent is already running") return nil } logger.Infof("Starting wsfc agent...") listenerAddr, err := net.ResolveTCPAddr("tcp", ":"+a.port) if err != nil { return err } listener, err := net.ListenTCP("tcp", listenerAddr) if err != nil { return err } // goroutine for handling request go func() { for { conn, err := listener.Accept() if err != nil { // if err is not due to listener closed, return if opErr, ok := err.(*net.OpError); ok && strings.Contains(opErr.Error(), "closed") { logger.Infof("wsfc agent - tcp listener closed.") return } logger.Errorf("wsfc agent - error on accepting request: %s", err) continue } a.waitGroup.Add(1) go a.handleHealthCheckRequest(conn) } }() logger.Infof("wsfc agent started. Listening on port: %s", a.port) a.listener = listener return nil } // Handle health check request. // The request payload is WSFC ip address. // Sendback 1 if ipaddress is found locally and 0 otherwise. func (a *wsfcAgent) handleHealthCheckRequest(conn net.Conn) { defer closer(conn) defer a.waitGroup.Done() conn.SetDeadline(time.Now().Add(time.Second)) buf := make([]byte, 1024) // Read the incoming connection into the buffer. reqLen, err := conn.Read(buf) if err != nil { logger.Errorf("wsfc - error on processing tcp request for network heartbeat health check: %s", err) return } wsfcIP := strings.TrimSpace(string(buf[:reqLen])) reply, err := checkIPExist(wsfcIP) if err != nil { logger.Errorf("wsfc - error on checking local ip: %s", err) } conn.Write([]byte(reply)) } // Stop agent. Will wait for all existing request to be completed. func (a *wsfcAgent) stop() error { if a.getState() == stopped { logger.Infof("wsfc agent already stopped.") return nil } logger.Infof("Stopping wsfc agent...") // close listener first to avoid taking additional request err := a.listener.Close() // wait for exiting request to finish a.waitGroup.Wait() a.listener = nil logger.Infof("wsfc agent stopped.") return err } // Get the current state of the agent. If there is a valid listener, // return state running and if listener is nil, return stopped func (a *wsfcAgent) getState() agentState { if a.listener != nil { return running } return stopped } func (a *wsfcAgent) getPort() string { return a.port } func (a *wsfcAgent) setPort(newPort string) { if newPort != a.port { logger.Infof("update wsfc agent from port %v to %v", a.port, newPort) a.port = newPort } } // Create wsfc agent only once func getWsfcAgentInstance() *wsfcAgent { once.Do(func() { agentInstance = &wsfcAgent{ port: wsfcDefaultAgentPort, waitGroup: &sync.WaitGroup{}, listener: nil, } }) return agentInstance } // help func to check whether the ip exists on local host. func checkIPExist(ip string) (string, error) { addrs, err := net.InterfaceAddrs() if err != nil { return "0", err } for _, address := range addrs { if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { ipString := ipnet.IP.To4().String() if ip == ipString { return "1", nil } } } return "0", nil } guest-agent-20231004.02/google_guest_agent/wsfc_test.go000066400000000000000000000213241450737260700226200ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "bufio" "context" "errors" "fmt" "net" "reflect" "testing" "github.com/GoogleCloudPlatform/guest-agent/metadata" ) func setEnableWSFC(md metadata.Descriptor, enabled *bool) *metadata.Descriptor { md.Instance.Attributes.EnableWSFC = enabled return &md } func setWSFCAddresses(md metadata.Descriptor, wsfcAddresses string) *metadata.Descriptor { md.Instance.Attributes.WSFCAddresses = wsfcAddresses return &md } func setWSFCAgentPort(md metadata.Descriptor, wsfcPort string) *metadata.Descriptor { md.Instance.Attributes.WSFCAgentPort = wsfcPort return &md } var ( testAgent = getWsfcAgentInstance() testMetadata = metadata.Descriptor{} testListener = &net.TCPListener{} ) func TestNewWsfcManager(t *testing.T) { type args struct { newMetadata *metadata.Descriptor } tests := []struct { name string args args want *wsfcManager }{ {"empty meta config", args{&testMetadata}, &wsfcManager{agentNewState: stopped, agentNewPort: wsfcDefaultAgentPort, agent: testAgent}}, {"wsfc enabled", args{setEnableWSFC(testMetadata, mkptr(true))}, &wsfcManager{agentNewState: running, agentNewPort: wsfcDefaultAgentPort, agent: testAgent}}, {"wsfc addrs is set", args{setWSFCAddresses(testMetadata, "0.0.0.0")}, &wsfcManager{agentNewState: running, agentNewPort: wsfcDefaultAgentPort, agent: testAgent}}, {"wsfc port is set", args{setWSFCAgentPort(testMetadata, "1818")}, &wsfcManager{agentNewState: stopped, agentNewPort: "1818", agent: testAgent}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { newMetadata = tt.args.newMetadata if got := newWsfcManager(); !reflect.DeepEqual(got, tt.want) { t.Errorf("newWsfcManager() = %v, want %v", got, tt.want) } }) } } func TestWsfcManagerDiff(t *testing.T) { tests := []struct { name string m *wsfcManager want bool }{ {"state change from stop to running", &wsfcManager{agentNewState: running, agent: &wsfcAgent{listener: nil}}, true}, {"state change from running to stop", &wsfcManager{agentNewState: stopped, agent: &wsfcAgent{listener: testListener}}, true}, {"port changed", &wsfcManager{agentNewPort: "1818", agent: &wsfcAgent{port: wsfcDefaultAgentPort}}, true}, {"state does not change both running", &wsfcManager{agentNewState: running, agent: &wsfcAgent{listener: testListener}}, false}, {"state does not change both stopped", &wsfcManager{agentNewState: stopped, agent: &wsfcAgent{listener: nil}}, false}, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.m.Diff(ctx) if err != nil { t.Errorf("Failed to run wsfcManager's Diff() call: %+v", err) } if got != tt.want { t.Errorf("test case %q: wsfcManager.diff() = %v, want %v", tt.name, got, tt.want) } }) } } // Mock health agent for unit testing type mockAgent struct { state agentState port string runError bool stopError bool runInvoked bool stopInvoked bool } func (a *mockAgent) getState() agentState { return a.state } func (a *mockAgent) getPort() string { return a.port } func (a *mockAgent) setPort(newPort string) { a.port = newPort } func (a *mockAgent) run() error { a.runInvoked = true if a.runError { return errors.New("Run error") } a.state = running return nil } func (a *mockAgent) stop() error { a.stopInvoked = true if a.stopError { return errors.New("Stop error") } a.state = stopped return nil } func TestWsfcManagerSet(t *testing.T) { tests := []struct { name string m *wsfcManager wantErr bool runInvoked bool stopInvoked bool }{ {"set start agent", &wsfcManager{agentNewState: running, agent: &mockAgent{state: stopped}}, false, true, false}, {"set start agent error", &wsfcManager{agentNewState: running, agent: &mockAgent{state: stopped, runError: true}}, true, true, false}, {"set stop agent", &wsfcManager{agentNewState: stopped, agent: &mockAgent{state: running}}, false, false, true}, {"set stop agent error", &wsfcManager{agentNewState: stopped, agent: &mockAgent{state: running, stopError: true}}, true, false, true}, {"set restart agent", &wsfcManager{agentNewState: running, agentNewPort: "1", agent: &mockAgent{state: running, port: "0"}}, false, true, true}, {"set restart agent stop error", &wsfcManager{agentNewState: running, agentNewPort: "1", agent: &mockAgent{state: running, port: "0", stopError: true}}, true, false, true}, {"set restart agent start error", &wsfcManager{agentNewState: running, agentNewPort: "1", agent: &mockAgent{state: running, port: "0", runError: true}}, true, true, true}, {"set do nothing", &wsfcManager{agentNewState: stopped, agentNewPort: "1", agent: &mockAgent{state: stopped, port: "0"}}, false, false, false}, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := tt.m.Set(ctx); (err != nil) != tt.wantErr { t.Errorf("wsfcManager.set() error = %v, wantErr %v", err, tt.wantErr) } mAgent := tt.m.agent.(*mockAgent) if gotRunInvoked := mAgent.runInvoked; gotRunInvoked != tt.runInvoked { t.Errorf("wsfcManager.set() runInvoked = %v, want %v", gotRunInvoked, tt.runInvoked) } if gotStopInvoked := mAgent.stopInvoked; gotStopInvoked != tt.stopInvoked { t.Errorf("wsfcManager.set() stopInvoked = %v, want %v", gotStopInvoked, tt.stopInvoked) } if tt.m.agentNewPort != mAgent.port { t.Errorf("wsfcManager.set() does not set prot, agent port = %v, want %v", mAgent.port, tt.m.agentNewPort) } }) } } func getHealthCheckResponce(request string, agent healthAgent) (string, error) { serverAddr := "localhost:" + agent.getPort() conn, err := net.Dial("tcp", serverAddr) if err != nil { return "", err } defer closer(conn) fmt.Fprint(conn, request) return bufio.NewReader(conn).ReadString('\n') } func TestWsfcRunAgentE2E(t *testing.T) { ctx := context.Background() wsfcMgr := &wsfcManager{ agentNewState: running, agentNewPort: wsfcDefaultAgentPort, agent: getWsfcAgentInstance(), } if err := wsfcMgr.Set(ctx); err != nil { t.Errorf("Failed to run wsfcManager's Set() call: %+v", err) } // make sure the agent is cleaned up. defer wsfcMgr.agent.stop() addrs, err := net.InterfaceAddrs() if err != nil { t.Fatal("getting localing interface failed.") } // pick first local ip that is not lookback ip var existIP string for _, addr := range addrs { if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { existIP = ipnet.IP.To4().String() break } } // test with existing IP if got, err := getHealthCheckResponce(existIP, wsfcMgr.agent); got != "1" { t.Errorf("health check failed with %v, got = %v, want %v", existIP, got, "1") if err != nil { t.Error(err) } } // test an invalid ip which could not exist invalidIP := "255.255.255.256" if got, err := getHealthCheckResponce(invalidIP, wsfcMgr.agent); got != "0" { t.Errorf("health check failed with %v, got = %v, want %v", invalidIP, got, "0") if err != nil { t.Error(err) } } // test stop agent wsfcMgrStop := &wsfcManager{ agentNewState: stopped, agent: getWsfcAgentInstance(), } if err := wsfcMgrStop.Set(ctx); err != nil { t.Errorf("Failed to run wsfcMgr's Set() call: %+v", err) } if _, err := getHealthCheckResponce(existIP, wsfcMgr.agent); err == nil { t.Errorf("health check still running after calling stop") } } func TestInvokeRunOnRunningWsfcAgent(t *testing.T) { agent := &wsfcAgent{listener: testListener} if err := agent.run(); err != nil { t.Errorf("Invoke run on running agent, error = %v, want = %v", err, nil) } } func TestInvokeStopOnStoppedWsfcAgent(t *testing.T) { agent := &wsfcAgent{listener: nil} if err := agent.stop(); err != nil { t.Errorf("Invoke stop on stopped agent, error = %v, want = %v", err, nil) } } func TestWsfcAgentSetPort(t *testing.T) { want := "2" agent := &wsfcAgent{port: "1"} agent.setPort(want) if agent.port != want { t.Errorf("WsfcAgent.setPort() port = %v, want %v", agent.port, want) } } func TestGetWsfcAgentInstance(t *testing.T) { agentFirst := getWsfcAgentInstance() agentSecond := getWsfcAgentInstance() if agentFirst != agentSecond { t.Errorf("getWsfcAgentInstance is not returning same instance") } } guest-agent-20231004.02/google_metadata_script_runner/000077500000000000000000000000001450737260700225165ustar00rootroot00000000000000guest-agent-20231004.02/google_metadata_script_runner/main.go000066400000000000000000000330351450737260700237750ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. // GCEMetadataScripts handles the running of metadata scripts on Google Compute // Engine instances. package main // TODO: compare log outputs in this utility to linux. // TODO: standardize and consolidate retries. import ( "bufio" "context" "encoding/json" "fmt" "io" "net" "net/http" "net/url" "os" "os/exec" "path/filepath" "regexp" "runtime" "strings" "time" "cloud.google.com/go/storage" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "github.com/go-ini/ini" ) var ( programName = "GCEMetadataScripts" version = "dev" metadataURL = "http://169.254.169.254/computeMetadata/v1" metadataHang = "/?recursive=true&alt=json&timeout_sec=10&last_etag=NONE" defaultTimeout = 20 * time.Second powerShellArgs = []string{"-NoProfile", "-NoLogo", "-ExecutionPolicy", "Unrestricted", "-File"} errUsage = fmt.Errorf("no valid arguments specified. Specify one of \"startup\", \"shutdown\" or \"specialize\"") config *ini.File storageURL = "storage.googleapis.com" bucket = `([a-z0-9][-_.a-z0-9]*)` object = `(.+)` // Many of the Google Storage URLs are supported below. // It is preferred that customers specify their object using // its gs:/// URL. bucketRegex = regexp.MustCompile(fmt.Sprintf(`^gs://%s/?$`, bucket)) gsRegex = regexp.MustCompile(fmt.Sprintf(`^gs://%s/%s$`, bucket, object)) // Check for the Google Storage URLs: // http://.storage.googleapis.com/ // https://.storage.googleapis.com/ gsHTTPRegex1 = regexp.MustCompile(fmt.Sprintf(`^http[s]?://%s\.storage\.googleapis\.com/%s$`, bucket, object)) // http://storage.cloud.google.com// // https://storage.cloud.google.com// gsHTTPRegex2 = regexp.MustCompile(fmt.Sprintf(`^http[s]?://storage\.cloud\.google\.com/%s/%s$`, bucket, object)) // Check for the other possible Google Storage URLs: // http://storage.googleapis.com// // https://storage.googleapis.com// // // The following are deprecated but also checked: // http://commondatastorage.googleapis.com// // https://commondatastorage.googleapis.com// gsHTTPRegex3 = regexp.MustCompile(fmt.Sprintf(`^http[s]?://(?:commondata)?storage\.googleapis\.com/%s/%s$`, bucket, object)) testStorageClient *storage.Client ) const ( winConfigPath = `C:\Program Files\Google\Compute Engine\instance_configs.cfg` configPath = `/etc/default/instance_configs.cfg` ) func newStorageClient(ctx context.Context) (*storage.Client, error) { if testStorageClient != nil { return testStorageClient, nil } return storage.NewClient(ctx) } func downloadGSURL(ctx context.Context, bucket, object string, file *os.File) error { client, err := newStorageClient(ctx) if err != nil { return fmt.Errorf("failed to create storage client: %v", err) } defer client.Close() r, err := client.Bucket(bucket).Object(object).NewReader(ctx) if err != nil { return fmt.Errorf("error reading object %q: %v", object, err) } defer r.Close() _, err = io.Copy(file, r) return err } func downloadURL(url string, file *os.File) error { // Retry up to 3 times, only wait 1 second between retries. var res *http.Response var err error for i := 1; ; i++ { res, err = http.Get(url) if err != nil && i > 3 { return err } if err == nil { break } time.Sleep(1 * time.Second) } defer res.Body.Close() if res.StatusCode != http.StatusOK { return fmt.Errorf("GET %q, bad status: %s", url, res.Status) } _, err = io.Copy(file, res.Body) return err } func downloadScript(ctx context.Context, path string, file *os.File) error { // Startup scripts may run before DNS is running on some systems, // particularly once a system is promoted to a domain controller. // Try to lookup storage.googleapis.com and sleep for up to 100s if // we get an error. // TODO: do we need to do this on every script? for i := 0; i < 20; i++ { if _, err := net.LookupHost(storageURL); err == nil { break } time.Sleep(5 * time.Second) } bucket, object := parseGCS(path) if bucket != "" && object != "" { // TODO: why is this retry outer, but downloadURL retry is inner? // Retry up to 3 times, only wait 1 second between retries. for i := 1; ; i++ { err := downloadGSURL(ctx, bucket, object, file) if err == nil { return nil } if err != nil && i > 3 { logger.Infof("Failed to download GCS path: %v", err) break } time.Sleep(1 * time.Second) } logger.Infof("Trying unauthenticated download") path = fmt.Sprintf("https://%s/%s/%s", storageURL, bucket, object) } // Fall back to an HTTP GET of the URL. return downloadURL(path, file) } func parseGCS(path string) (string, string) { for _, re := range []*regexp.Regexp{gsRegex, gsHTTPRegex1, gsHTTPRegex2, gsHTTPRegex3} { match := re.FindStringSubmatch(path) if len(match) == 3 { return match[1], match[2] } } return "", "" } func getMetadataKey(key string) (string, error) { md, err := getMetadata(key, false) if err != nil { return "", err } return string(md), nil } func getMetadataAttributes(key string) (map[string]string, error) { md, err := getMetadata(key, true) if err != nil { return nil, err } var att map[string]string return att, json.Unmarshal(md, &att) } func getMetadata(key string, recurse bool) ([]byte, error) { client := &http.Client{ Timeout: defaultTimeout, } url := metadataURL + key if recurse { url += metadataHang } req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Add("Metadata-Flavor", "Google") var res *http.Response // Retry forever, increase sleep between retries (up to 5 times) in order // to wait for slow network initialization. var rt time.Duration for i := 1; ; i++ { res, err = client.Do(req) if err == nil { break } if i < 6 { rt = time.Duration(3*i) * time.Second } logger.Errorf("error connecting to metadata server, retrying in %s, error: %v", rt, err) time.Sleep(rt) } defer res.Body.Close() md, err := io.ReadAll(res.Body) if err != nil { return nil, err } return md, nil } func normalizeFilePathForWindows(filePath string, metadataKey string, gcsScriptURL *url.URL) string { // If either the metadataKey ends in one of these extensions OR if this is a url startup script and if the // url path ends in one of these extensions, append the extension to the filePath name so that Windows can recognize it. for _, ext := range []string{"bat", "cmd", "ps1", "exe"} { if strings.HasSuffix(metadataKey, "-"+ext) || (gcsScriptURL != nil && strings.HasSuffix(gcsScriptURL.Path, "."+ext)) { filePath = fmt.Sprintf("%s.%s", filePath, ext) break } } return filePath } func writeScriptToFile(ctx context.Context, value string, filePath string, gcsScriptURL *url.URL) error { // Create or download files. if gcsScriptURL != nil { file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { return fmt.Errorf("error opening temp file: %v", err) } if err := downloadScript(ctx, value, file); err != nil { file.Close() return err } if err := file.Close(); err != nil { return fmt.Errorf("error closing temp file: %v", err) } } else { // Trim leading spaces and newlines. value = strings.TrimLeft(value, " \n\v\f\t\r") if err := os.WriteFile(filePath, []byte(value), 0755); err != nil { return fmt.Errorf("error writing temp file: %v", err) } } return nil } func setupAndRunScript(ctx context.Context, metadataKey string, value string) error { // Make sure that the URL is valid for URL startup scripts var gcsScriptURL *url.URL if strings.HasSuffix(metadataKey, "-url") { var err error gcsScriptURL, err = url.Parse(strings.TrimSpace(value)) if err != nil { return err } } // Make temp directory. tmpDir, err := os.MkdirTemp(config.Section("MetadataScripts").Key("run_dir").String(), "metadata-scripts") if err != nil { return err } defer os.RemoveAll(tmpDir) tmpFile := filepath.Join(tmpDir, metadataKey) if runtime.GOOS == "windows" { tmpFile = normalizeFilePathForWindows(tmpFile, metadataKey, gcsScriptURL) } writeScriptToFile(ctx, value, tmpFile, gcsScriptURL) return runScript(tmpFile, metadataKey) } // Craft the command to run. func runScript(filePath string, metadataKey string) error { var cmd *exec.Cmd if strings.HasSuffix(filePath, ".ps1") { cmd = exec.Command("powershell.exe", append(powerShellArgs, filePath)...) } else { if runtime.GOOS == "windows" { cmd = exec.Command(filePath) } else { cmd = exec.Command(config.Section("MetadataScripts").Key("default_shell").MustString("/bin/bash"), "-c", filePath) } } return runCmd(cmd, metadataKey) } func runCmd(c *exec.Cmd, name string) error { pr, pw, err := os.Pipe() if err != nil { return err } defer pr.Close() c.Stdout = pw c.Stderr = pw if err := c.Start(); err != nil { return err } pw.Close() in := bufio.NewScanner(pr) for { if !in.Scan() { if err := in.Err(); err != nil { logger.Errorf("error while communicating with %q script: %v", name, err) } break } logger.Log(logger.LogEntry{ Message: fmt.Sprintf("%s: %s", name, in.Text()), CallDepth: 3, Severity: logger.Info, }) } pr.Close() return c.Wait() } // getWantedKeys returns the list of keys to check for a given type of script and OS. func getWantedKeys(args []string, os string) ([]string, error) { if len(args) != 2 { return nil, errUsage } prefix := args[1] switch prefix { case "specialize": prefix = "sysprep-specialize" case "startup", "shutdown": if os == "windows" { prefix = "windows-" + prefix } if !config.Section("MetadataScripts").Key(prefix).MustBool(true) { return nil, fmt.Errorf("%s scripts disabled in instance config", prefix) } default: return nil, errUsage } var mdkeys []string var suffixes []string if os == "windows" { suffixes = []string{"ps1", "cmd", "bat", "url"} } else { suffixes = []string{"url"} // The 'bare' startup-script or shutdown-script key, not supported on Windows. mdkeys = append(mdkeys, fmt.Sprintf("%s-script", prefix)) } for _, suffix := range suffixes { mdkeys = append(mdkeys, fmt.Sprintf("%s-script-%s", prefix, suffix)) } return mdkeys, nil } func parseMetadata(md map[string]string, wanted []string) map[string]string { found := make(map[string]string) for _, key := range wanted { val, ok := md[key] if !ok || val == "" { continue } found[key] = val } return found } // getExistingKeys returns the wanted keys that are set in metadata. func getExistingKeys(wanted []string) (map[string]string, error) { for _, attrs := range []string{"/instance/attributes", "/project/attributes"} { md, err := getMetadataAttributes(attrs) if err != nil { return nil, err } if found := parseMetadata(md, wanted); len(found) != 0 { return found, nil } } return nil, nil } func logFormatWindows(e logger.LogEntry) string { now := time.Now().Format("2006/01/02 15:04:05") // 2006/01/02 15:04:05 GCEMetadataScripts This is a log message. return fmt.Sprintf("%s %s: %s", now, programName, e.Message) } func parseConfig(file string) (*ini.File, error) { // Priority: file.cfg, file.cfg.distro, file.cfg.template cfg, err := ini.LoadSources(ini.LoadOptions{Loose: true, Insensitive: true}, file, file+".distro", file+".template") if err != nil { return nil, err } return cfg, nil } func main() { ctx := context.Background() opts := logger.LogOpts{LoggerName: programName} cfgfile := configPath if runtime.GOOS == "windows" { cfgfile = winConfigPath opts.Writers = []io.Writer{&utils.SerialPort{Port: "COM1"}, os.Stdout} opts.FormatFunction = logFormatWindows } else { opts.Writers = []io.Writer{os.Stdout} opts.FormatFunction = func(e logger.LogEntry) string { return e.Message } // Local logging is syslog; we will just use stdout in Linux. opts.DisableLocalLogging = true } var err error config, err = parseConfig(cfgfile) if err != nil && !os.IsNotExist(err) { fmt.Printf("Error parsing instance config %s: %s\n", cfgfile, err.Error()) } // The keys to check vary based on the argument and the OS. Also functions to validate arguments. wantedKeys, err := getWantedKeys(os.Args, runtime.GOOS) if err != nil { fmt.Printf("%s\n", err.Error()) os.Exit(2) } projectID, err := getMetadataKey("/project/project-id") if err == nil { opts.ProjectName = projectID } logger.Init(ctx, opts) logger.Infof("Starting %s scripts (version %s).", os.Args[1], version) scripts, err := getExistingKeys(wantedKeys) if err != nil { logger.Fatalf(err.Error()) } if len(scripts) == 0 { logger.Infof("No %s scripts to run.", os.Args[1]) return } for _, wantedKey := range wantedKeys { value, ok := scripts[wantedKey] if !ok { continue } logger.Infof("Found %s in metadata.", wantedKey) if err := setupAndRunScript(ctx, wantedKey, value); err != nil { logger.Infof("%s %s", wantedKey, err) continue } logger.Infof("%s exit status 0", wantedKey) } logger.Infof("Finished running %s scripts.", os.Args[1]) } guest-agent-20231004.02/google_metadata_script_runner/main_test.go000066400000000000000000000144551450737260700250410ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package main import ( "fmt" "net/http" "net/http/httptest" "net/url" "os" "reflect" "testing" ) func TestMain(m *testing.M) { config, _ = parseConfig("") os.Exit(m.Run()) } func TestGetWantedArgs(t *testing.T) { getWantedTests := []struct { arg string os string want []string }{ { "specialize", "windows", []string{ "sysprep-specialize-script-ps1", "sysprep-specialize-script-cmd", "sysprep-specialize-script-bat", "sysprep-specialize-script-url", }, }, { "startup", "windows", []string{ "windows-startup-script-ps1", "windows-startup-script-cmd", "windows-startup-script-bat", "windows-startup-script-url", }, }, { "shutdown", "windows", []string{ "windows-shutdown-script-ps1", "windows-shutdown-script-cmd", "windows-shutdown-script-bat", "windows-shutdown-script-url", }, }, { "startup", "linux", []string{ "startup-script", "startup-script-url", }, }, { "shutdown", "linux", []string{ "shutdown-script", "shutdown-script-url", }, }, } for _, tt := range getWantedTests { got, err := getWantedKeys([]string{"", tt.arg}, tt.os) if err != nil { t.Fatalf("validateArgs returned error: %v", err) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("returned slice does not match expected one: got %v, want %v", got, tt.want) } _, err = getWantedKeys([]string{""}, "") if err == nil { t.Errorf("0 args should produce an error") } _, err = getWantedKeys([]string{"", "", ""}, "") if err == nil { t.Errorf("3 args should produce an error") } } } func TestGetExistingKeys(t *testing.T) { wantedKeys := []string{ "sysprep-specialize-script-cmd", "sysprep-specialize-script-ps1", "sysprep-specialize-script-bat", "sysprep-specialize-script-url", } md := map[string]string{ "sysprep-specialize-script-cmd": "cmd", "startup-script-cmd": "cmd", "shutdown-script-ps1": "ps1", "sysprep-specialize-script-url": "url", "sysprep-specialize-script-ps1": "ps1", "key": "value", "sysprep-specialize-script-bat": "bat", } want := map[string]string{ "sysprep-specialize-script-ps1": "ps1", "sysprep-specialize-script-cmd": "cmd", "sysprep-specialize-script-bat": "bat", "sysprep-specialize-script-url": "url", } got := parseMetadata(md, wantedKeys) if !reflect.DeepEqual(got, want) { t.Errorf("parsed metadata does not match expectation, got: %v, want: %v", got, want) } } func TestParseGCS(t *testing.T) { matchTests := []struct { path, bucket, object string }{ {"gs://bucket/object", "bucket", "object"}, {"gs://bucket/some/object", "bucket", "some/object"}, {"http://bucket.storage.googleapis.com/object", "bucket", "object"}, {"https://bucket.storage.googleapis.com/object", "bucket", "object"}, {"https://bucket.storage.googleapis.com/some/object", "bucket", "some/object"}, {"http://storage.googleapis.com/bucket/object", "bucket", "object"}, {"http://commondatastorage.googleapis.com/bucket/object", "bucket", "object"}, {"https://storage.googleapis.com/bucket/object", "bucket", "object"}, {"https://commondatastorage.googleapis.com/bucket/object", "bucket", "object"}, {"https://storage.googleapis.com/bucket/some/object", "bucket", "some/object"}, {"https://commondatastorage.googleapis.com/bucket/some/object", "bucket", "some/object"}, } for _, tt := range matchTests { bucket, object := parseGCS(tt.path) if bucket != tt.bucket { t.Errorf("returned bucket does not match expected one for %q:\n got %q, want: %q", tt.path, bucket, tt.bucket) } if object != tt.object { t.Errorf("returned object does not match expected one for %q\n: got %q, want: %q", tt.path, object, tt.object) } } } func TestGetMetadata(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"key1":"value1","key2":"value2"}`) })) defer ts.Close() metadataURL = ts.URL metadataHang = "" want := map[string]string{"key1": "value1", "key2": "value2"} got, err := getMetadataAttributes("") if err != nil { t.Fatalf("error running getMetadataAttributes: %v", err) } if !reflect.DeepEqual(got, want) { t.Errorf("metadata does not match expectation, got: %q, want: %q", got, want) } } func TestNormalizeFilePathForWindows(t *testing.T) { tmpFilePath := "C:/Temp/file" testCases := []struct { metadataKey string gcsScriptURLPath string want string }{ { metadataKey: "windows-startup-script-url", gcsScriptURLPath: "gs://gcs-bucket/binary.exe", want: "C:/Temp/file.exe", }, { metadataKey: "windows-startup-script-url", gcsScriptURLPath: "gs://gcs-bucket/binary", want: "C:/Temp/file", }, { metadataKey: "windows-startup-script-ps1", gcsScriptURLPath: "gs://gcs-bucket/binary.ps1", want: "C:/Temp/file.ps1", }, { metadataKey: "windows-startup-script-ps1", gcsScriptURLPath: "gs://gcs-bucket/binary", want: "C:/Temp/file.ps1", }, { metadataKey: "windows-startup-script-bat", gcsScriptURLPath: "gs://gcs-bucket/binary.bat", want: "C:/Temp/file.bat", }, { metadataKey: "windows-startup-script-cmd", gcsScriptURLPath: "gs://gcs-bucket/binary.cmd", want: "C:/Temp/file.cmd", }, } for _, tc := range testCases { url := url.URL{ Path: tc.gcsScriptURLPath, } got := normalizeFilePathForWindows(tmpFilePath, tc.metadataKey, &url) if got != tc.want { t.Errorf("Return didn't match expected output for inputs:\n fileName: %s, metadataKey: %s, gcsScriptUrl: %s\n Expected: %s\n Got: %s", tmpFilePath, tc.metadataKey, tc.gcsScriptURLPath, tc.want, got) } } } guest-agent-20231004.02/instance_configs.cfg000066400000000000000000000015341450737260700204250ustar00rootroot00000000000000[Accounts] deprovision_remove = false gpasswd_add_cmd = gpasswd -a {user} {group} gpasswd_remove_cmd = gpasswd -d {user} {group} groupadd_cmd = groupadd {group} groups = adm,dip,docker,lxd,plugdev,video useradd_cmd = useradd -m -s /bin/bash -p * {user} userdel_cmd = userdel -r {user} [Daemons] accounts_daemon = true clock_skew_daemon = true network_daemon = true [InstanceSetup] host_key_types = ecdsa,ed25519,rsa network_enabled = true optimize_local_ssd = true set_boto_config = true set_host_keys = true set_multiqueue = true [IpForwarding] ethernet_proto_id = 66 ip_aliases = true target_instance_ips = true [MetadataScripts] default_shell = /bin/bash run_dir = shutdown = true startup = true [NetworkInterfaces] dhclient_script = /sbin/google-dhclient-script dhcp_command = ip_forwarding = true setup = true [OSLogin] cert_authentication = true guest-agent-20231004.02/metadata/000077500000000000000000000000001450737260700162055ustar00rootroot00000000000000guest-agent-20231004.02/metadata/metadata.go000066400000000000000000000277271450737260700203330ustar00rootroot00000000000000// Copyright 2017 Google LLC // 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 // https://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. package metadata import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" "strings" "time" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) const ( defaultMetadataURL = "http://169.254.169.254/computeMetadata/v1/" defaultEtag = "NONE" // defaultHangtimeout is the timeout parameter passed to metadata as the hang timeout. defaultHangTimeout = 60 // defaultClientTimeout sets the http.Client time out, the delta of 10s between the // defaultHangTimeout and client timeout should be enough to avoid canceling the context // before headers and body are read. defaultClientTimeout = 70 ) var ( // we backoff until 10s backoffDuration = 100 * time.Millisecond backoffAttempts = 100 ) // MDSClientInterface is the minimum required Metadata Server interface for Guest Agent. type MDSClientInterface interface { Get(context.Context) (*Descriptor, error) GetKey(context.Context, string, map[string]string) (string, error) Watch(context.Context) (*Descriptor, error) WriteGuestAttributes(context.Context, string, string) error } // requestConfig is used internally to configure an http request given its context. type requestConfig struct { baseURL string hang bool recursive bool jsonOutput bool timeout int headers map[string]string } // Client defines the public interface between the core guest agent and // the metadata layer. type Client struct { metadataURL string etag string httpClient *http.Client } // New allocates and configures a new Client instance. func New() *Client { return &Client{ metadataURL: defaultMetadataURL, etag: defaultEtag, httpClient: &http.Client{ Timeout: defaultClientTimeout * time.Second, }, } } // Descriptor wraps/holds all the metadata keys, the structure reflects the json // descriptor returned with metadata call with alt=jason. type Descriptor struct { Instance Instance Project Project } // UnmarshalJSON unmarshals b into Descritor. func (m *Descriptor) UnmarshalJSON(b []byte) error { // We can't unmarshal into metadata directly as it would create an infinite loop. type temp Descriptor var t temp err := json.Unmarshal(b, &t) if err == nil { *m = Descriptor(t) return nil } // If this is a syntax error return a useful error. sErr, ok := err.(*json.SyntaxError) if !ok { return err } // Byte number where the error line starts. start := bytes.LastIndex(b[:sErr.Offset], []byte("\n")) + 1 // Assume end byte of error line is EOF unless this isn't the last line. end := len(b) if i := bytes.Index(b[start:], []byte("\n")); i >= 0 { end = start + i } // Position of error in line (where to place the '^'). pos := int(sErr.Offset) - start if pos != 0 { pos = pos - 1 } return fmt.Errorf("JSON syntax error: %s \n%s\n%s^", err, b[start:end], strings.Repeat(" ", pos)) } type virtualClock struct { DriftToken int `json:"drift-token"` } // Instance describes the metadata's instance attributes/keys. type Instance struct { ID json.Number MachineType string Attributes Attributes NetworkInterfaces []NetworkInterfaces VirtualClock virtualClock } // NetworkInterfaces describes the instances network interfaces configurations. type NetworkInterfaces struct { ForwardedIps []string ForwardedIpv6s []string TargetInstanceIps []string IPAliases []string Mac string DHCPv6Refresh string } // Project describes the projects instance's attributes. type Project struct { Attributes Attributes ProjectID string NumericProjectID json.Number } // Attributes describes the project's attributes keys. type Attributes struct { BlockProjectKeys bool EnableOSLogin *bool EnableWindowsSSH *bool TwoFactor *bool SecurityKey *bool SSHKeys []string WindowsKeys WindowsKeys Diagnostics string DisableAddressManager *bool DisableAccountManager *bool EnableDiagnostics *bool EnableWSFC *bool WSFCAddresses string WSFCAgentPort string DisableTelemetry bool } // UnmarshalJSON unmarshals b into Attribute. func (a *Attributes) UnmarshalJSON(b []byte) error { var mkbool = func(value bool) *bool { res := new(bool) *res = value return res } // Unmarshal to literal JSON types before doing anything else. type inner struct { BlockProjectKeys string `json:"block-project-ssh-keys"` Diagnostics string `json:"diagnostics"` DisableAccountManager string `json:"disable-account-manager"` DisableAddressManager string `json:"disable-address-manager"` EnableDiagnostics string `json:"enable-diagnostics"` EnableOSLogin string `json:"enable-oslogin"` EnableWindowsSSH string `json:"enable-windows-ssh"` EnableWSFC string `json:"enable-wsfc"` OldSSHKeys string `json:"sshKeys"` SSHKeys string `json:"ssh-keys"` TwoFactor string `json:"enable-oslogin-2fa"` SecurityKey string `json:"enable-oslogin-sk"` WindowsKeys WindowsKeys `json:"windows-keys"` WSFCAddresses string `json:"wsfc-addrs"` WSFCAgentPort string `json:"wsfc-agent-port"` DisableTelemetry string `json:"disable-guest-telemetry"` } var temp inner if err := json.Unmarshal(b, &temp); err != nil { return err } a.Diagnostics = temp.Diagnostics a.WSFCAddresses = temp.WSFCAddresses a.WSFCAgentPort = temp.WSFCAgentPort a.WindowsKeys = temp.WindowsKeys value, err := strconv.ParseBool(temp.BlockProjectKeys) if err == nil { a.BlockProjectKeys = value } value, err = strconv.ParseBool(temp.EnableDiagnostics) if err == nil { a.EnableDiagnostics = mkbool(value) } value, err = strconv.ParseBool(temp.DisableAccountManager) if err == nil { a.DisableAccountManager = mkbool(value) } value, err = strconv.ParseBool(temp.DisableAddressManager) if err == nil { a.DisableAddressManager = mkbool(value) } value, err = strconv.ParseBool(temp.EnableOSLogin) if err == nil { a.EnableOSLogin = mkbool(value) } value, err = strconv.ParseBool(temp.EnableWindowsSSH) if err == nil { a.EnableWindowsSSH = mkbool(value) } value, err = strconv.ParseBool(temp.EnableWSFC) if err == nil { a.EnableWSFC = mkbool(value) } value, err = strconv.ParseBool(temp.TwoFactor) if err == nil { a.TwoFactor = mkbool(value) } value, err = strconv.ParseBool(temp.SecurityKey) if err == nil { a.SecurityKey = mkbool(value) } value, err = strconv.ParseBool(temp.DisableTelemetry) if err == nil { a.DisableTelemetry = value } // So SSHKeys will be nil instead of []string{} if temp.SSHKeys != "" { a.SSHKeys = strings.Split(temp.SSHKeys, "\n") } if temp.OldSSHKeys != "" { a.BlockProjectKeys = true a.SSHKeys = append(a.SSHKeys, strings.Split(temp.OldSSHKeys, "\n")...) } return nil } func (c *Client) updateEtag(resp *http.Response) bool { oldEtag := c.etag c.etag = resp.Header.Get("etag") if c.etag == "" { c.etag = defaultEtag } return c.etag != oldEtag } func shouldRetry(resp *http.Response, err error) bool { // If the context was canceled just return the error and don't retry. if err != nil && errors.Is(err, context.Canceled) { return false } // Known non-retriable status codes. if resp != nil && resp.StatusCode == 404 { return false } return true } func (c *Client) retry(ctx context.Context, cfg requestConfig) (string, error) { var ferr error for i := 1; i <= backoffAttempts; i++ { resp, err := c.do(ctx, cfg) ferr = err // Check if error is retriable, if not just return the error and don't retry. if err != nil && !shouldRetry(resp, err) { return "", err } // Apply the backoff strategy. if err != nil { logger.Debugf("Attempt %d: failed to connect to metadata server: %+v", i, err) time.Sleep(time.Duration(i) * backoffDuration) continue } defer resp.Body.Close() md, err := io.ReadAll(resp.Body) if err != nil { ferr = err logger.Debugf("Attempt %d: failed to read metadata server response bytes: %+v", i, err) time.Sleep(time.Duration(i) * backoffDuration) continue } return string(md), nil } logger.Errorf("Exhausted %d retry attempts to connect to MDS, failed with an error: %+v", backoffAttempts, ferr) return "", fmt.Errorf("reached max attempts to connect to metadata") } // GetKey gets a specific metadata key. func (c *Client) GetKey(ctx context.Context, key string, headers map[string]string) (string, error) { reqURL, err := url.JoinPath(c.metadataURL, key) if err != nil { return "", fmt.Errorf("failed to form metadata url: %+v", err) } cfg := requestConfig{ baseURL: reqURL, headers: headers, } return c.retry(ctx, cfg) } // Watch runs a longpoll on metadata server. func (c *Client) Watch(ctx context.Context) (*Descriptor, error) { return c.get(ctx, true) } // Get does a metadata call, if hang is set to true then it will do a longpoll. func (c *Client) Get(ctx context.Context) (*Descriptor, error) { return c.get(ctx, false) } func (c *Client) get(ctx context.Context, hang bool) (*Descriptor, error) { cfg := requestConfig{ baseURL: c.metadataURL, timeout: defaultHangTimeout, recursive: true, jsonOutput: true, } if hang { cfg.hang = true } resp, err := c.retry(ctx, cfg) if err != nil { return nil, err } var ret Descriptor if err = json.Unmarshal([]byte(resp), &ret); err != nil { return nil, err } return &ret, nil } // WriteGuestAttributes does a put call to mds changing a guest attribute value. func (c *Client) WriteGuestAttributes(ctx context.Context, key, value string) error { logger.Debugf("write guest attribute %q", key) finalURL, err := url.JoinPath(c.metadataURL, "instance/guest-attributes/", key) if err != nil { return fmt.Errorf("failed to form metadata url: %+v", err) } logger.Debugf("Requesting(PUT) MDS URL: %s", finalURL) req, err := http.NewRequest("PUT", finalURL, strings.NewReader(value)) if err != nil { return err } req.Header.Add("Metadata-Flavor", "Google") req = req.WithContext(ctx) _, err = c.httpClient.Do(req) return err } func (c *Client) do(ctx context.Context, cfg requestConfig) (*http.Response, error) { finalURL, err := url.Parse(cfg.baseURL) if err != nil { return nil, fmt.Errorf("failed to parse url: %+v", err) } values := finalURL.Query() if cfg.hang { values.Add("wait_for_change", "true") values.Add("last_etag", c.etag) } if cfg.timeout > 0 { values.Add("timeout_sec", fmt.Sprintf("%d", cfg.timeout)) } if cfg.recursive { values.Add("recursive", "true") } if cfg.jsonOutput { values.Add("alt", "json") } finalURL.RawQuery = values.Encode() logger.Debugf("Requesting(GET) MDS URL: %s", finalURL.String()) req, err := http.NewRequestWithContext(ctx, "GET", finalURL.String(), nil) if err != nil { return nil, err } req.Header.Add("Metadata-Flavor", "Google") for k, v := range cfg.headers { req.Header.Add(k, v) } resp, err := c.httpClient.Do(req) // If we are canceling httpClient will also wrap the context's error so // check first the context. if ctx.Err() != nil { return resp, ctx.Err() } if err != nil { return resp, fmt.Errorf("error connecting to metadata server: %+v", err) } statusCodeMsg := "error connecting to metadata server, status code: %d" switch resp.StatusCode { case 404, 412: return resp, fmt.Errorf(statusCodeMsg, resp.StatusCode) } if cfg.hang { c.updateEtag(resp) } return resp, nil } guest-agent-20231004.02/metadata/metadata_test.go000066400000000000000000000130401450737260700213510ustar00rootroot00000000000000// Copyright 2018 Google LLC // 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 // https://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. package metadata import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "reflect" "testing" "time" ) func TestWatchMetadata(t *testing.T) { etag1, etag2 := "foo", "bar" var req int et := time.Now().Add(10 * time.Second).Format(time.RFC3339) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if req == 0 { w.Header().Set("etag", etag1) } else { w.Header().Set("etag", etag2) } fmt.Fprintf(w, `{"instance":{"attributes":{"enable-oslogin":"true","ssh-keys":"name:ssh-rsa [KEY] hostname\nname:ssh-rsa [KEY] hostname","windows-keys":"{}\n{\"expireOn\":\"%[1]s\",\"exponent\":\"exponent\",\"modulus\":\"modulus\",\"username\":\"username\"}\n{\"expireOn\":\"%[1]s\",\"exponent\":\"exponent\",\"modulus\":\"modulus\",\"username\":\"username\",\"addToAdministrators\":true}","wsfc-addrs":"foo"}}}`, et) req++ })) defer ts.Close() client := &Client{ metadataURL: ts.URL, httpClient: &http.Client{ Timeout: 1 * time.Second, }, } truebool := new(bool) *truebool = true want := Attributes{ EnableOSLogin: truebool, WSFCAddresses: "foo", WindowsKeys: WindowsKeys{ WindowsKey{Exponent: "exponent", UserName: "username", Modulus: "modulus", ExpireOn: et, AddToAdministrators: nil}, WindowsKey{Exponent: "exponent", UserName: "username", Modulus: "modulus", ExpireOn: et, AddToAdministrators: func() *bool { ret := true; return &ret }()}, }, SSHKeys: []string{"name:ssh-rsa [KEY] hostname", "name:ssh-rsa [KEY] hostname"}, DisableTelemetry: false, } for _, e := range []string{etag1, etag2} { got, err := client.Watch(context.Background()) if err != nil { t.Fatalf("error running watchMetadata: %v", err) } gotA := got.Instance.Attributes if !reflect.DeepEqual(gotA, want) { t.Fatalf("Did not parse expected metadata.\ngot:\n'%+v'\nwant:\n'%+v'", gotA, want) } if client.etag != e { t.Fatalf("etag not updated as expected (%q != %q)", client.etag, e) } } } func TestBlockProjectKeys(t *testing.T) { tests := []struct { json string res bool }{ { `{"instance": {"attributes": {"ssh-keys": "name:ssh-rsa [KEY] hostname\nname:ssh-rsa [KEY] hostname"},"project": {"attributes": {"ssh-keys": "name:ssh-rsa [KEY] hostname\nname:ssh-rsa [KEY] hostname"}}}}`, false, }, { `{"instance": {"attributes": {"sshKeys": "name:ssh-rsa [KEY] hostname\nname:ssh-rsa [KEY] hostname"},"project": {"attributes": {"ssh-keys": "name:ssh-rsa [KEY] hostname\nname:ssh-rsa [KEY] hostname"}}}}`, true, }, { `{"instance": {"attributes": {"block-project-ssh-keys": "true", "ssh-keys": "name:ssh-rsa [KEY] hostname\nname:ssh-rsa [KEY] hostname"},"project": {"attributes": {"ssh-keys": "name:ssh-rsa [KEY] hostname\nname:ssh-rsa [KEY] hostname"}}}}`, true, }, } for _, test := range tests { var md Descriptor if err := json.Unmarshal([]byte(test.json), &md); err != nil { t.Errorf("failed to unmarshal JSON: %v", err) } if md.Instance.Attributes.BlockProjectKeys != test.res { t.Errorf("instance-level sshKeys didn't set block-project-keys (got %t expected %t)", md.Instance.Attributes.BlockProjectKeys, test.res) } } } func TestGetKey(t *testing.T) { var gotHeaders http.Header var gotReqURI string wantValue := "value" handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotHeaders = r.Header gotReqURI = r.RequestURI fmt.Fprint(w, wantValue) }) testsrv := httptest.NewServer(handler) defer testsrv.Close() client := New() client.metadataURL = testsrv.URL key := "key" wantURI := "/" + key headers := map[string]string{"key": "value"} gotValue, err := client.GetKey(context.Background(), key, headers) if err != nil { t.Fatal(err) } headers["Metadata-Flavor"] = "Google" for k, v := range headers { if gotHeaders.Get(k) != v { t.Fatalf("received headers does not contain all expected headers, want: %q, got: %q", headers, gotHeaders) } } if wantValue != gotValue { t.Errorf("did not get expected return value, got :%q, want: %q", gotValue, wantValue) } if gotReqURI != wantURI { t.Errorf("did not get expected request uri, got :%q, want: %q", gotReqURI, wantURI) } } func TestShouldRetry(t *testing.T) { tests := []struct { desc string resp *http.Response err error want bool }{ { desc: "404_should_not_retry", resp: &http.Response{StatusCode: 404}, want: false, err: nil, }, { desc: "429_should_retry", resp: &http.Response{StatusCode: 429}, want: true, err: nil, }, { desc: "ctx_canceled_should_not_retry", resp: &http.Response{StatusCode: 200}, want: false, err: context.Canceled, }, { desc: "random_err_should_retry", resp: &http.Response{StatusCode: 200}, want: true, err: fmt.Errorf("fake retriable error"), }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { if got := shouldRetry(test.resp, test.err); got != test.want { t.Errorf("shouldRetry(%+v, %+v) = %t, want %t", test.resp, test.err, got, test.want) } }) } } guest-agent-20231004.02/metadata/windows.go000066400000000000000000000032461450737260700202330ustar00rootroot00000000000000// Copyright 2023 Google LLC // // 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. package metadata import ( "encoding/json" "strings" "github.com/GoogleCloudPlatform/guest-agent/utils" "github.com/GoogleCloudPlatform/guest-logging-go/logger" ) // WindowsKey describes the WindowsKey metadata keys. type WindowsKey struct { Email string ExpireOn string Exponent string Modulus string UserName string HashFunction string AddToAdministrators *bool PasswordLength int } // WindowsKeys is a slice of WindowKey. type WindowsKeys []WindowsKey // UnmarshalJSON unmarshals b into WindowsKeys. func (k *WindowsKeys) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } for _, jskey := range strings.Split(s, "\n") { var wk WindowsKey if err := json.Unmarshal([]byte(jskey), &wk); err != nil { logger.Errorf("failed to unmarshal windows key from metadata: %s", err) continue } expired, _ := utils.CheckExpired(wk.ExpireOn) if wk.Exponent != "" && wk.Modulus != "" && wk.UserName != "" && !expired { *k = append(*k, wk) } } return nil } guest-agent-20231004.02/packaging/000077500000000000000000000000001450737260700163515ustar00rootroot00000000000000guest-agent-20231004.02/packaging/debian/000077500000000000000000000000001450737260700175735ustar00rootroot00000000000000guest-agent-20231004.02/packaging/debian/changelog000066400000000000000000000002701450737260700214440ustar00rootroot00000000000000google-guest-agent (1:20191204.00-g1) stable; urgency=medium * Extend windows agent for multi platform. -- Google Cloud Team Wed, 04 Dec 2019 12:00:00 +0000 guest-agent-20231004.02/packaging/debian/compat000066400000000000000000000000031450737260700207720ustar00rootroot0000000000000010 guest-agent-20231004.02/packaging/debian/control000066400000000000000000000007741450737260700212060ustar00rootroot00000000000000Source: google-guest-agent Maintainer: Google Cloud Team Section: misc Priority: optional Standards-Version: 3.9.8 Build-Depends: debhelper (>= 9.20160709), dh-golang (>= 1.1), golang-go Package: google-guest-agent Architecture: any Depends: ${misc:Depends}, google-compute-engine-oslogin (>= 1:20231003) Conflicts: python-google-compute-engine, python3-google-compute-engine Description: Google Compute Engine Guest Agent Contains the guest agent and metadata script runner binaries. guest-agent-20231004.02/packaging/debian/copyright000066400000000000000000000017241450737260700215320ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: google-compute-engine Upstream-Contact: gc-team@google.com Files: * Copyright: Copyright 2017 Google LLC License: Apache-2.0 Files: debian/* Copyright: Copyright 2017 Google LLC License: Apache-2.0 License: Apache-2.0 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. . On Debian systems, the complete text of the Apache version 2.0 license can be found in "/usr/share/common-licenses/Apache-2.0". guest-agent-20231004.02/packaging/debian/google-guest-agent.docs000066400000000000000000000000261450737260700241400ustar00rootroot00000000000000THIRD_PARTY_LICENSES/ guest-agent-20231004.02/packaging/debian/postinst000066400000000000000000000003151450737260700214000ustar00rootroot00000000000000#!/bin/sh -e if [ "$1" = "configure" ] ; then if [ ! -f /etc/default/instance_configs.cfg ]; then cp -a "/usr/share/${DPKG_MAINTSCRIPT_PACKAGE}/instance_configs.cfg" /etc/default/ fi fi #DEBHELPER# guest-agent-20231004.02/packaging/debian/postrm000066400000000000000000000002321450737260700210370ustar00rootroot00000000000000#!/bin/sh -e if [ "$1" = "purge" ] ; then if [ -f /etc/default/instance_configs.cfg ]; then rm /etc/default/instance_configs.cfg fi fi #DEBHELPER# guest-agent-20231004.02/packaging/debian/rules000077500000000000000000000036731450737260700206640ustar00rootroot00000000000000#!/usr/bin/make -f export PATH := /tmp/go/bin:$(PATH) export SHELL := env PATH=$(PATH) /bin/bash export DH_OPTIONS export DH_GOPKG := github.com/GoogleCloudPlatform/guest-agent export CGO_ENABLED := 0 export GOCACHE := /tmp/.cache export GOPROXY := https://proxy.golang.org export GO111MODULE := on export GOPATH := /usr/share/gocode export DH_GOLANG_BUILDPKG := github.com/GoogleCloudPlatform/guest-agent/google_guest_agent github.com/GoogleCloudPlatform/guest-agent/google_metadata_script_runner github.com/GoogleCloudPlatform/guest-agent/gce_workload_cert_refresh %: dh $@ --buildsystem=golang --with=golang,systemd override_dh_auto_install: # Binary-only package. dh_auto_install -- --no-source install -d debian/google-guest-agent/usr/share/google-guest-agent install -p -m 0644 instance_configs.cfg debian/google-guest-agent/usr/share/google-guest-agent install -d debian/google-guest-agent/lib/systemd/system install -p -m 0644 gce-workload-cert-refresh.timer debian/google-guest-agent/lib/systemd/system/ override_dh_golang: # We don't use any packaged dependencies, so skip dh_golang step. override_dh_auto_build: dh_auto_build -O--buildsystem=golang -- -ldflags="-s -w -X main.version=$(VERSION)" -mod=readonly override_dh_installinit: # We don't ship sysvinit files or need script changes for them. override_dh_systemd_enable: install -d debian/google-guest-agent/lib/systemd/system install -p -m 0644 *.service debian/google-guest-agent/lib/systemd/system/ install -d debian/google-guest-agent/lib/systemd/system-preset install -p -m 0644 *.preset debian/google-guest-agent/lib/systemd/system-preset/ dh_systemd_enable google-guest-agent.service google-startup-scripts.service google-shutdown-scripts.service gce-workload-cert-refresh.timer override_dh_systemd_start: # Only perform start/stop actions for the guest agent and cert refresh timer. dh_systemd_start google-guest-agent.service dh_systemd_start gce-workload-cert-refresh.timer guest-agent-20231004.02/packaging/debian/source/000077500000000000000000000000001450737260700210735ustar00rootroot00000000000000guest-agent-20231004.02/packaging/debian/source/format000066400000000000000000000000141450737260700223010ustar00rootroot000000000000003.0 (quilt) guest-agent-20231004.02/packaging/googet/000077500000000000000000000000001450737260700176355ustar00rootroot00000000000000guest-agent-20231004.02/packaging/googet/agent_install.ps1000066400000000000000000000043011450737260700231040ustar00rootroot00000000000000# Copyright 2017 Google LLC # # 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 # # https://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. $name = 'GCEAgent' $path = '"C:\Program Files\Google\Compute Engine\agent\GCEWindowsAgent.exe"' $display_name = 'Google Compute Engine Agent' $description = 'Google Compute Engine Agent' $initial_config = @' # GCE Instance Configuration # For details on what can be configured, see: # https://cloud.google.com/compute/docs/instances/windows/creating-managing-windows-instances#configure-windows-features # [accountManager] # disable=false # [addressManager] # disable=false '@ function Set-ServiceConfig { # Restart service after 1s, then 2s. Reset error counter after 60s. sc.exe failure $name reset= 60 actions= restart/1000/restart/2000 # Set dependency and delayed start cmd.exe /c "sc.exe config ${name} depend= `"samss`" start= delayed-auto binpath= \`"${path}\`"" # Create trigger to start the service on first IP address sc.exe triggerinfo $name start/networkon } try { if (-not (Get-Service $name -ErrorAction SilentlyContinue)) { New-Service -Name $name ` -DisplayName $display_name ` -BinaryPathName $path ` -StartupType Automatic ` -Description $description } else { Set-Service -Name $name ` -DisplayName $display_name ` -Description $description } Set-ServiceConfig $config = "${env:ProgramFiles}\Google\Compute Engine\instance_configs.cfg" if (-not (Test-Path $config)) { $initial_config | Set-Content -Path $config -Encoding ASCII } Restart-Service $name -Verbose } catch { Write-Output $_.InvocationInfo.PositionMessage Write-Output "Install failed: $($_.Exception.Message)" exit 1 } guest-agent-20231004.02/packaging/googet/agent_uninstall.ps1000066400000000000000000000012021450737260700234440ustar00rootroot00000000000000# Copyright 2017 Google LLC # # 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 # # https://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. Stop-Service GCEAgent -Verbose & sc.exe delete GCEAgent guest-agent-20231004.02/packaging/googet/google-compute-engine-metadata-scripts.goospec000066400000000000000000000042571450737260700306620ustar00rootroot00000000000000{ "name": "google-compute-engine-metadata-scripts", "version": "{{.version}}.0@1", "arch": "x86_64", "authors": "Google LLC", "license": "http://www.apache.org/licenses/LICENSE-2.0", "description": "Google Compute Engine metadata scripts", "source": "https://github.com/GoogleCloudPlatform/guest-agent/", "files": { "google_metadata_script_runner/GCEMetadataScripts.exe": "/Google/Compute Engine/metadata_scripts/GCEMetadataScripts.exe", "packaging/googet/run_shutdown_scripts.cmd": "/Google/Compute Engine/metadata_scripts/run_shutdown_scripts.cmd", "packaging/googet/run_startup_scripts.cmd": "/Google/Compute Engine/metadata_scripts/run_startup_scripts.cmd", "THIRD_PARTY_LICENSES": "/Google/Compute Engine/THIRD_PARTY_LICENSES/", "LICENSE": "/Google/Compute Engine/metadata_scripts/LICENSE.txt" }, "install": { "path": "packaging/googet/metadata_scripts_install.ps1" }, "uninstall": { "path": "packaging/googet/metadata_scripts_uninstall.ps1" }, "releaseNotes": [ "4.3.0 - Use guest logging library.", "4.2.0 - Retry forever if metadata server is unavailable.", " - Honor project level metadata scripts.", "4.1.6 - Add additional retry logic to account for slow network startup and transient GCS errors.", "4.1.3 - Add a sleep in case DNS is not started at boot.", "4.1.0 - Add startup and shutdown settings.", "4.0.0 - Rewrite GCEMetadataScripts in Go.", "3.5.0 - Separate metadata-scripts into its own package." ], "sources": [{ "include": [ "google_metadata_script_runner/GCEMetadataScripts.exe", "packaging/googet/run_shutdown_scripts.cmd", "packaging/googet/run_startup_scripts.cmd", "packaging/googet/metadata_scripts_install.ps1", "packaging/googet/metadata_scripts_uninstall.ps1", "THIRD_PARTY_LICENSES/**", "LICENSE" ] }], "build": { "linux": "/bin/bash", "linuxArgs": ["-c", "GOOS=windows /tmp/go/bin/go build -ldflags '-X main.version={{.version}}' -mod=readonly -o ./google_metadata_script_runner/GCEMetadataScripts.exe ./google_metadata_script_runner"] } } guest-agent-20231004.02/packaging/googet/google-compute-engine-windows.goospec000066400000000000000000000040601450737260700270770ustar00rootroot00000000000000{ "name": "google-compute-engine-windows", "version": "{{.version}}.0@1", "arch": "x86_64", "authors": "Google LLC", "license": "http://www.apache.org/licenses/LICENSE-2.0", "description": "Google Compute Engine Windows agent", "source": "https://github.com/GoogleCloudPlatform/guest-agent/", "files": { "GCEWindowsAgent.exe": "/Google/Compute Engine/agent/GCEWindowsAgent.exe", "GCEAuthorizedKeysCommand.exe": "/Google/Compute Engine/agent/GCEAuthorizedKeysCommand.exe", "THIRD_PARTY_LICENSES": "/Google/Compute Engine/THIRD_PARTY_LICENSES/", "LICENSE": "/Google/Compute Engine/agent/LICENSE.txt" }, "install": { "path": "packaging/googet/agent_install.ps1" }, "uninstall": { "path": "packaging/googet/agent_uninstall.ps1" }, "releaseNotes": [ "4.7.0 - Use guest logging library.", "4.6.0 - Support for internal IP address load balancing.", "4.5.2 - When resetting a users password only lookup the user on the local system", "4.5.0 - Allow specfiying hash function for password reset, sha1, sha256, or sha512", "4.4.0 - Run updates on every metadata return, including timeout", " - Add individual manager timeout to address manager", "4.3.0 - Add diagnostics support for optional log extraction", "4.2.3 - Don't error on lack of config file, only run update on updated metadata", " - Make DNS and Network errors more user friendly", "4.2.0 - Use *UnicastIpAddressEntry functions for managing forwarded IPs", "4.1.0 - Add configuration file", "4.0.0 - Rewrite GCEAgent in Go", "3.5.0 - Pull the Windows agent out into its own package" ], "sources": [{ "include": [ "GCEWindowsAgent.exe", "GCEAuthorizedKeysCommand.exe", "packaging/googet/agent_install.ps1", "packaging/googet/agent_uninstall.ps1", "THIRD_PARTY_LICENSES/**", "LICENSE" ] }], "build": { "linux": "/bin/bash", "linuxArgs": ["-c", "./packaging/googet/windows_agent_build.sh {{.version}}"] } } guest-agent-20231004.02/packaging/googet/metadata_scripts_install.ps1000066400000000000000000000040451450737260700253420ustar00rootroot00000000000000# Copyright 2017 Google LLC # # 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 # # https://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. $install_dir = "${env:ProgramFiles}\Google\Compute Engine\metadata_scripts" $machine_env = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' $path = (Get-ItemProperty $machine_env).Path if ($path -notlike "*${install_dir}*") { Set-ItemProperty $machine_env -Name 'Path' -Value ($path + ";${install_dir}") } $run_startup_scripts = "${install_dir}\run_startup_scripts.cmd" $service = New-Object -ComObject("Schedule.Service") $service.Connect() $task = $service.NewTask(0) $task.Settings.Enabled = $true $task.Settings.AllowDemandStart = $true $task.Settings.Priority = 5 $action = $task.Actions.Create(0) $action.Path = "`"$run_startup_scripts`"" $trigger = $task.Triggers.Create(8) $folder = $service.GetFolder('\') $folder.RegisterTaskDefinition('GCEStartup',$task,6,'System',$null,5) | Out-Null $gpt_ini = "${env:SystemRoot}\System32\GroupPolicy\gpt.ini" $scripts_ini = "${env:SystemRoot}\System32\GroupPolicy\Machine\Scripts\scripts.ini" if ((Test-Path $gpt_ini) -or (Test-Path $scripts_ini)) { return } New-Item -Type Directory -Path "${env:SystemRoot}\System32\GroupPolicy\Machine\Scripts" -ErrorAction SilentlyContinue @' [General] gPCMachineExtensionNames= [{42B5FAAE-6536-11D2-AE5A-0000F87571E3}{40B6664F-4972-11D1-A7CA-0000F87571E3}] Version=1 '@ | Set-Content -Path $gpt_ini -Encoding ASCII @' [Shutdown] 0CmdLine=C:\Program Files\Google\Compute Engine\metadata_scripts\run_shutdown_scripts.cmd 0Parameters= '@ | Set-Content -Path $scripts_ini -Encoding ASCII guest-agent-20231004.02/packaging/googet/metadata_scripts_uninstall.ps1000066400000000000000000000016751450737260700257130ustar00rootroot00000000000000# Copyright 2017 Google LLC # # 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 # # https://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. $install_dir = "${env:ProgramFiles}\Google\Compute Engine\metadata_scripts" $machine_env = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' $path = (Get-ItemProperty $machine_env).Path if ($path -like "*${install_dir}*") { Set-ItemProperty $machine_env -Name 'Path' -Value $path.Replace(";$install_dir", '') } & schtasks /delete /tn GCEStartup /f guest-agent-20231004.02/packaging/googet/run_shutdown_scripts.cmd000066400000000000000000000014361450737260700246340ustar00rootroot00000000000000@echo off REM Copyright 2015 Google LLC REM REM Licensed under the Apache License, Version 2.0 (the "License"); REM you may not use this file except in compliance with the License. REM You may obtain a copy of the License at REM REM https://www.apache.org/licenses/LICENSE-2.0 REM REM Unless required by applicable law or agreed to in writing, software REM distributed under the License is distributed on an "AS IS" BASIS, REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. REM See the License for the specific language governing permissions and REM limitations under the License. REM Run shutdown scripts that should happen as soon as the instance REM begins to power down "C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe" "shutdown" guest-agent-20231004.02/packaging/googet/run_startup_scripts.cmd000066400000000000000000000017041450737260700244610ustar00rootroot00000000000000@echo off REM Copyright 2015 Google LLC REM REM Licensed under the Apache License, Version 2.0 (the "License"); REM you may not use this file except in compliance with the License. REM You may obtain a copy of the License at REM REM https://www.apache.org/licenses/LICENSE-2.0 REM REM Unless required by applicable law or agreed to in writing, software REM distributed under the License is distributed on an "AS IS" BASIS, REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. REM See the License for the specific language governing permissions and REM limitations under the License. REM Run startup scripts that should happen late at boot. REM A scheduled task may only run for up to three days before termination. REM We execute the startup script asynchronously so it may run without REM this three day maximum runtime limitation. start "" "C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe" "startup" guest-agent-20231004.02/packaging/googet/windows_agent_build.sh000077500000000000000000000017301450737260700242240ustar00rootroot00000000000000# Copyright 2023 Google LLC # # 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 # # https://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. #!/bin/bash # Should be run from the guest-agent directory. # Usage: # ./packaging/googet/windows_agent_build.sh version=$1 GOOS=windows /tmp/go/bin/go build -ldflags "-X main.version=$version" -mod=readonly -o GCEWindowsAgent.exe ./google_guest_agent GOOS=windows /tmp/go/bin/go build -ldflags "-X main.version=$version" -mod=readonly -o GCEAuthorizedKeysCommand.exe ./google_authorized_keys guest-agent-20231004.02/packaging/google-guest-agent.spec000066400000000000000000000133351450737260700227270ustar00rootroot00000000000000# Copyright 2018 Google LLC # 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 # https://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. # Don't build debuginfo packages. %define debug_package %{nil} Name: google-guest-agent Epoch: 1 Version: %{_version} Release: g1%{?dist} Summary: Google Compute Engine guest agent. License: ASL 2.0 Url: https://cloud.google.com/compute/docs/images/guest-environment Source0: %{name}_%{version}.orig.tar.gz Requires: google-compute-engine-oslogin >= 1:20231003 BuildArch: %{_arch} %if ! 0%{?el6} BuildRequires: systemd %endif Obsoletes: python-google-compute-engine, python3-google-compute-engine %description Contains the Google guest agent binary. %prep %autosetup %build for bin in google_guest_agent google_metadata_script_runner gce_workload_cert_refresh; do pushd "$bin" GOPATH=%{_gopath} CGO_ENABLED=0 %{_go} build -ldflags="-s -w -X main.version=%{_version}" -mod=readonly popd done %install install -d "%{buildroot}/%{_docdir}/%{name}" cp -r THIRD_PARTY_LICENSES "%buildroot/%_docdir/%name/THIRD_PARTY_LICENSES" install -d %{buildroot}%{_bindir} install -p -m 0755 google_guest_agent/google_guest_agent %{buildroot}%{_bindir}/google_guest_agent install -p -m 0755 google_metadata_script_runner/google_metadata_script_runner %{buildroot}%{_bindir}/google_metadata_script_runner install -p -m 0755 gce_workload_cert_refresh/gce_workload_cert_refresh %{buildroot}%{_bindir}/gce_workload_cert_refresh install -d %{buildroot}/usr/share/google-guest-agent install -p -m 0644 instance_configs.cfg %{buildroot}/usr/share/google-guest-agent/instance_configs.cfg %if 0%{?el6} install -d %{buildroot}/etc/init install -p -m 0644 %{name}.conf %{buildroot}/etc/init/ install -p -m 0644 google-startup-scripts.conf %{buildroot}/etc/init/ install -p -m 0644 google-shutdown-scripts.conf %{buildroot}/etc/init/ %else install -d %{buildroot}%{_unitdir} install -d %{buildroot}%{_presetdir} install -p -m 0644 %{name}.service %{buildroot}%{_unitdir} install -p -m 0644 google-startup-scripts.service %{buildroot}%{_unitdir} install -p -m 0644 google-shutdown-scripts.service %{buildroot}%{_unitdir} install -p -m 0644 gce-workload-cert-refresh.service %{buildroot}%{_unitdir} install -p -m 0644 gce-workload-cert-refresh.timer %{buildroot}%{_unitdir} install -p -m 0644 90-%{name}.preset %{buildroot}%{_presetdir}/90-%{name}.preset %endif %files %{_docdir}/%{name} %defattr(-,root,root,-) /usr/share/google-guest-agent/instance_configs.cfg %{_bindir}/google_guest_agent %{_bindir}/google_metadata_script_runner %{_bindir}/gce_workload_cert_refresh %if 0%{?el6} /etc/init/%{name}.conf /etc/init/google-startup-scripts.conf /etc/init/google-shutdown-scripts.conf %else %{_unitdir}/%{name}.service %{_unitdir}/google-startup-scripts.service %{_unitdir}/google-shutdown-scripts.service %{_unitdir}/gce-workload-cert-refresh.service %{_unitdir}/gce-workload-cert-refresh.timer %{_presetdir}/90-%{name}.preset %endif %if ! 0%{?el6} %post if [ $1 -eq 1 ]; then # Initial installation # Install instance configs if not already present. if [ ! -f /etc/default/instance_configs.cfg ]; then cp -a /usr/share/google-guest-agent/instance_configs.cfg /etc/default/ fi # Use enable instead of preset because preset is not supported in # chroots. systemctl enable google-guest-agent.service >/dev/null 2>&1 || : systemctl enable google-startup-scripts.service >/dev/null 2>&1 || : systemctl enable google-shutdown-scripts.service >/dev/null 2>&1 || : systemctl enable gce-workload-cert-refresh.timer >/dev/null 2>&1 || : if [ -d /run/systemd/system ]; then systemctl daemon-reload >/dev/null 2>&1 || : systemctl start google-guest-agent.service >/dev/null 2>&1 || : systemctl start gce-workload-cert-refresh.timer >/dev/null 2>&1 || : fi else # Package upgrade if [ -d /run/systemd/system ]; then systemctl try-restart google-guest-agent.service >/dev/null 2>&1 || : fi fi %preun if [ $1 -eq 0 ]; then # Package removal, not upgrade systemctl --no-reload disable google-guest-agent.service >/dev/null 2>&1 || : systemctl --no-reload disable google-startup-scripts.service >/dev/null 2>&1 || : systemctl --no-reload disable google-shutdown-scripts.service >/dev/null 2>&1 || : systemctl --no-reload disable gce-workload-cert-refresh.timer >/dev/null 2>&1 || : if [ -d /run/systemd/system ]; then systemctl stop google-guest-agent.service >/dev/null 2>&1 || : fi fi %postun if [ $1 -eq 0 ]; then # Package removal, not upgrade if [ -f /etc/default/instance_configs.cfg ]; then rm /etc/default/instance_configs.cfg fi if [ -d /run/systemd/system ]; then systemctl daemon-reload >/dev/null 2>&1 || : fi fi %else # EL6 %post if [ $1 -eq 1 ]; then # Install instance configs if not already present. if [ ! -f /etc/default/instance_configs.cfg ]; then cp -a /usr/share/google-guest-agent/instance_configs.cfg /etc/default/ fi # Initial installation initctl start google-guest-agent >/dev/null 2>&1 || : else # Upgrade initctl restart google-guest-agent >/dev/null 2>&1 || : fi %preun if [ $1 -eq 0 ]; then # Package removal, not upgrade initctl stop google-guest-agent >/dev/null 2>&1 || : fi %postun if [ $1 -eq 0 ]; then # Package removal, not upgrade if [ -f /etc/default/instance_configs.cfg ]; then rm /etc/default/instance_configs.cfg fi fi %endif guest-agent-20231004.02/utils/000077500000000000000000000000001450737260700155655ustar00rootroot00000000000000guest-agent-20231004.02/utils/main.go000066400000000000000000000141131450737260700170400ustar00rootroot00000000000000// Copyright 2022 Google LLC // 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 // https://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. // Utilities for Google Guest Agent and Google Authorized Keys package utils import ( "encoding/json" "errors" "fmt" "io/fs" "os" "path/filepath" "regexp" "strings" "time" "github.com/tarm/serial" ) // ContainsString checks for the presence of a string in a slice. func ContainsString(s string, ss []string) bool { for _, a := range ss { if a == s { return true } } return false } type sshExpiration struct { ExpireOn string UserName string } // CheckExpiredKey validates whether a key has expired. // Keys with invalid expiration formats will result in an error. func CheckExpiredKey(key string) error { trimmedKey := strings.Trim(key, " ") if trimmedKey == "" { return errors.New("invalid ssh key entry - empty key") } fields := strings.SplitN(trimmedKey, " ", 4) if len(fields) < 3 { // Non-expiring key. return nil } if len(fields) == 3 && fields[2] == "google-ssh" { // expiring key without expiration format. return errors.New("invalid ssh key entry - expiration missing") } if len(fields) >= 3 && fields[2] != "google-ssh" { // Non-expiring key with an arbitrary comment part return nil } if len(fields) > 3 { lkey := sshExpiration{} if err := json.Unmarshal([]byte(fields[3]), &lkey); err != nil { // invalid expiration format. return err } expired, err := CheckExpired(lkey.ExpireOn) if err != nil { return err } if expired { return errors.New("invalid ssh key entry - expired key") } } return nil } // CheckExpired takes a time string and determines if it represents a time in the past. func CheckExpired(expireOn string) (bool, error) { t, err := time.Parse(time.RFC3339, expireOn) if err != nil { t2, err2 := time.Parse("2006-01-02T15:04:05-0700", expireOn) if err2 != nil { return true, err //Return RFC3339 error } t = t2 } return t.Before(time.Now()), nil } // ValidateUser checks for the presence of a characters which should not be // allowed in a username string, returns an error if any such characters are // detected, nil otherwise. // Currently, the only banned characters are whitespace characters. func ValidateUser(user string) error { if user == "" { return errors.New("invalid username - it is empty") } whiteSpaceRegexp, _ := regexp.Compile(`\s`) if whiteSpaceRegexp.MatchString(user) { return errors.New("invalid username - whitespace detected") } return nil } // GetUserKey returns a user and a SSH key if a rawKey has a correct format, nil otherwise. // It doesn't validate entries. func GetUserKey(rawKey string) (string, string, error) { key := strings.Trim(rawKey, " ") if key == "" { return "", "", errors.New("invalid ssh key entry - empty key") } idx := strings.Index(key, ":") if idx == -1 { return "", "", errors.New("invalid ssh key entry - unrecognized format. Expecting user:ssh-key") } user := key[:idx] if user == "" { return "", "", errors.New("invalid ssh key entry - user missing") } if key[idx+1:] == "" { return "", "", errors.New("invalid ssh key entry - key missing") } return user, key[idx+1:], nil } // ValidateUserKey takes an user and a key received from GetUserKey() and // validate the user for special characters and the key for expiration func ValidateUserKey(user, key string) error { if err := ValidateUser(user); err != nil { return err } if err := CheckExpiredKey(key); err != nil { return err } return nil } // SerialPort is a type for writing to a named serial port. type SerialPort struct { Port string } func (s *SerialPort) Write(b []byte) (int, error) { c := &serial.Config{Name: s.Port, Baud: 115200} p, err := serial.OpenPort(c) if err != nil { return 0, err } defer p.Close() return p.Write(b) } // WriteFile creates parent directories if required and writes content to the output file. func WriteFile(content []byte, outputFile string, perm fs.FileMode) error { if err := os.MkdirAll(filepath.Dir(outputFile), perm); err != nil { return fmt.Errorf("unable to create required directories for %q: %w", outputFile, err) } return os.WriteFile(outputFile, content, perm) } // SaferWriteFile writes to a temporary file and then replaces the expected output file. // This prevents other processes from reading partial content while the writer is still writing. func SaferWriteFile(content []byte, outputFile string, perm fs.FileMode) error { dir := filepath.Dir(outputFile) name := filepath.Base(outputFile) if err := os.MkdirAll(dir, perm); err != nil { return fmt.Errorf("unable to create required directories %q: %w", dir, err) } tmp, err := os.CreateTemp(dir, name+"*") if err != nil { return fmt.Errorf("unable to create temporary file under %q: %w", dir, err) } if err := os.Chmod(tmp.Name(), perm); err != nil { return fmt.Errorf("unable to set permissions on temporary file %q: %w", dir, err) } if err := tmp.Close(); err != nil { return fmt.Errorf("failed to close temporary file: %w", err) } if err := WriteFile(content, tmp.Name(), perm); err != nil { return fmt.Errorf("unable to write to a temporary file %q: %w", tmp.Name(), err) } return os.Rename(tmp.Name(), outputFile) } // CopyFile copies content from src to dst and sets permissions. func CopyFile(src, dst string, perm fs.FileMode) error { b, err := os.ReadFile(src) if err != nil { return fmt.Errorf("failed to read %q: %w", src, err) } if err := WriteFile(b, dst, perm); err != nil { return fmt.Errorf("failed to write %q: %w", dst, err) } if err := os.Chmod(dst, perm); err != nil { return fmt.Errorf("unable to set permissions on destination file %q: %w", dst, err) } return nil } guest-agent-20231004.02/utils/main_test.go000066400000000000000000000140141450737260700200770ustar00rootroot00000000000000// Copyright 2022 Google LLC // 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 // https://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. package utils import ( "os" "path/filepath" "testing" ) func TestContainsString(t *testing.T) { table := []struct { a string slice []string want bool }{ {"a", []string{"a", "b"}, true}, {"c", []string{"a", "b"}, false}, } for _, tt := range table { if got, want := ContainsString(tt.a, tt.slice), tt.want; got != want { t.Errorf("containsString(%s, %v) incorrect return: got %v, want %t", tt.a, tt.slice, got, want) } } } func TestGetUserKey(t *testing.T) { table := []struct { key string user string keyVal string haserr bool }{ {`usera:ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, "usera", `ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, false}, {" ", "", "", true}, {"ssh-rsa AAAA1234", "", "", true}, {":ssh-rsa AAAA1234", "", "", true}, {"userb:", "", "", true}, {"userc:ssh-rsa AAAA1234 info text", "userc", "ssh-rsa AAAA1234 info text", false}, } for _, tt := range table { u, k, err := GetUserKey(tt.key) e := err != nil if u != tt.user || k != tt.keyVal || e != tt.haserr { t.Errorf("GetUserKey(%s) incorrect return: got user: %s, key: %s, error: %v - want user: %s, key: %s, error: %v", tt.key, u, k, e, tt.user, tt.keyVal, tt.haserr) } } } func TestValidateUserKey(t *testing.T) { table := []struct { user string key string haserr bool }{ {"usera", `ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, false}, {"user a", `ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, true}, {"usera", `ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2021-04-23T12:34:56+0000"}`, true}, {"usera", `ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"Apri 4, 2056"}`, true}, {"usera", `ssh-rsa AAAA1234 google-ssh`, true}, {"usera", `ssh-rsa AAAA1234 test info`, false}, {" ", "", true}, {"", "ssh-rsa AAAA1234", true}, {"userb", "", true}, } for _, tt := range table { err := ValidateUserKey(tt.user, tt.key) e := err != nil if e != tt.haserr { t.Errorf("ValidateUserKey(%s, %s) incorrect return: expected: %t - got: %t", tt.user, tt.key, tt.haserr, e) } } } func TestCheckExpiredKey(t *testing.T) { table := []struct { key string expired bool }{ {`usera:ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, false}, {`usera:ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"2021-04-23T12:34:56+0000"}`, true}, {`usera:ssh-rsa AAAA1234 google-ssh {"userName":"usera@example.com","expireOn":"Apri 4, 2056"}`, true}, {`usera:ssh-rsa AAAA1234 google-ssh`, true}, {" ", true}, {"ssh-rsa AAAA1234", false}, {":ssh-rsa AAAA1234", false}, {"usera:ssh-rsa AAAA1234", false}, } for _, tt := range table { err := CheckExpiredKey(tt.key) isExpired := err != nil if isExpired != tt.expired { t.Errorf("CheckExpiredKey(%s) incorrect return: expired: %t - want expired: %t", tt.key, isExpired, tt.expired) } } } func TestValidateUser(t *testing.T) { table := []struct { user string valid bool }{ {"username", true}, {"username:key", true}, {"user -g", false}, {"user -g 27", false}, {"user\t-g", false}, {"user\n-g", false}, {"username\t-g\n27", false}, } for _, tt := range table { err := ValidateUser(tt.user) isValid := err == nil if isValid != tt.valid { t.Errorf("ValidateUser(%s) incorrect return: expected: %t - got: %t", tt.user, tt.valid, isValid) } } } func TestSaferWriteFile(t *testing.T) { f := filepath.Join(t.TempDir(), "file") want := "test-data" if err := SaferWriteFile([]byte(want), f, 0644); err != nil { t.Errorf("SaferWriteFile(%s, %s) failed unexpectedly with err: %+v", "test-data", f, err) } got, err := os.ReadFile(f) if err != nil { t.Errorf("os.ReadFile(%s) failed unexpectedly with err: %+v", f, err) } if string(got) != want { t.Errorf("os.ReadFile(%s) = %s, want %s", f, string(got), want) } i, err := os.Stat(f) if err != nil { t.Errorf("os.Stat(%s) failed unexpectedly with err: %+v", f, err) } if i.Mode().Perm() != 0o644 { t.Errorf("SaferWriteFile(%s) set incorrect permissions, os.Stat(%s) = %o, want %o", f, f, i.Mode().Perm(), 0o644) } } func TestCopyFile(t *testing.T) { tmp := t.TempDir() dst := filepath.Join(tmp, "dst") src := filepath.Join(tmp, "src") want := "testdata" if err := os.WriteFile(src, []byte(want), 0777); err != nil { t.Fatalf("failed to write test source file: %v", err) } if err := CopyFile(src, dst, 0644); err != nil { t.Errorf("CopyFile(%s, %s) failed unexpectedly with error: %v", src, dst, err) } got, err := os.ReadFile(dst) if err != nil { t.Errorf("unable to read %q: %v", dst, err) } if string(got) != want { t.Errorf("CopyFile(%s, %s) copied %q, expected %q", src, dst, string(got), want) } i, err := os.Stat(dst) if err != nil { t.Errorf("os.Stat(%s) failed unexpectedly with err: %+v", dst, err) } if i.Mode().Perm() != 0o644 { t.Errorf("SaferWriteFile(%s) set incorrect permissions, os.Stat(%s) = %o, want %o", dst, dst, i.Mode().Perm(), 0o644) } } func TestCopyFileError(t *testing.T) { tmp := t.TempDir() dst := filepath.Join(tmp, "dst") src := filepath.Join(tmp, "src") if err := CopyFile(src, dst, 0644); err == nil { t.Errorf("CopyFile(%s, %s) succeeded for non-existent file, want error", src, dst) } }