Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package google-guest-agent for 
openSUSE:Factory checked in at 2023-11-01 22:10:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/google-guest-agent (Old)
 and      /work/SRC/openSUSE:Factory/.google-guest-agent.new.17445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "google-guest-agent"

Wed Nov  1 22:10:49 2023 rev:29 rq:1121627 version:20231031.01

Changes:
--------
--- /work/SRC/openSUSE:Factory/google-guest-agent/google-guest-agent.changes    
2023-10-27 22:29:18.637973466 +0200
+++ 
/work/SRC/openSUSE:Factory/.google-guest-agent.new.17445/google-guest-agent.changes
 2023-11-01 22:11:21.638735085 +0100
@@ -1,0 +2,17 @@
+Wed Nov  1 14:05:15 UTC 2023 - John Paul Adrian Glaubitz 
<adrian.glaub...@suse.com>
+
+- Update to version 20231031.01 (bsc#1216547, bsc#1216751)
+  * Add prefix to scheduler logs (#325)
+- from version 20231030.00
+  * Test configuration files are loaded in the documented
+    order. Fix initial integration test. (#324)
+  * Enable mTLS by default (#323)
+- from version 20231026.00
+  * Rotate MDS root certificate (#322)
+- from version 20231020.00
+  * Update response struct, add tests (#315)
+  * Don't try to schedule mTLS job twice (#317)
+- from version 20231019.00
+  * snapshot: Add context cancellation handling (#318)
+
+-------------------------------------------------------------------
@@ -4 +21 @@
-- Bump the golang compiler version to 1.21
+- Bump the golang compiler version to 1.21 (bsc#1216546)

Old:
----
  guest-agent-20231016.00.tar.gz

New:
----
  guest-agent-20231031.01.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ google-guest-agent.spec ++++++
--- /var/tmp/diff_new_pack.MPrNRp/_old  2023-11-01 22:11:22.310759979 +0100
+++ /var/tmp/diff_new_pack.MPrNRp/_new  2023-11-01 22:11:22.314760128 +0100
@@ -24,7 +24,7 @@
 %global import_path     %{provider_prefix}
 
 Name:           google-guest-agent
-Version:        20231016.00
+Version:        20231031.01
 Release:        0
 Summary:        Google Cloud Guest Agent
 License:        Apache-2.0

++++++ _service ++++++
--- /var/tmp/diff_new_pack.MPrNRp/_old  2023-11-01 22:11:22.342761165 +0100
+++ /var/tmp/diff_new_pack.MPrNRp/_new  2023-11-01 22:11:22.346761313 +0100
@@ -3,8 +3,8 @@
     <param 
name="url">https://github.com/GoogleCloudPlatform/guest-agent/</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="versionformat">20231016.00</param>
-    <param name="revision">20231016.00</param>
+    <param name="versionformat">20231031.01</param>
+    <param name="revision">20231031.01</param>
     <param name="changesgenerate">enable</param>
   </service>
   <service name="recompress" mode="disabled">
@@ -15,7 +15,7 @@
     <param name="basename">guest-agent</param>
   </service>
   <service name="go_modules" mode="disabled">
-    <param name="archive">guest-agent-20231016.00.tar.gz</param>
+    <param name="archive">guest-agent-20231031.01.tar.gz</param>
   </service>
 </services>
 

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.MPrNRp/_old  2023-11-01 22:11:22.366762054 +0100
+++ /var/tmp/diff_new_pack.MPrNRp/_new  2023-11-01 22:11:22.370762202 +0100
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/GoogleCloudPlatform/guest-agent/</param>
-              <param 
name="changesrevision">fbd7766921b8cef749943d1bf70ffafe4f516098</param></service></servicedata>
+              <param 
name="changesrevision">7a1a1cf54884237cd2883e8a4daff8402b08df18</param></service></servicedata>
 (No newline at EOF)
 

++++++ guest-agent-20231016.00.tar.gz -> guest-agent-20231031.01.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/guest-agent-20231016.00/.gitignore 
new/guest-agent-20231031.01/.gitignore
--- old/guest-agent-20231016.00/.gitignore      2023-10-16 20:47:54.000000000 
+0200
+++ new/guest-agent-20231031.01/.gitignore      2023-10-31 20:11:57.000000000 
+0100
@@ -1,5 +1,15 @@
-# ignore all built binaries
+# Ignore all built binaries.
 **/gce_workload_cert_refresh
+**/gce_workload_cert_refresh.exe
 **/google_authorized_keys
+**/google_authorized_keys.exe
 **/google_guest_agent
+**/google_guest_agent.exe
 **/google_metadata_script_runner
+**/google_metadata_script_runner.exe
+
+# Don't ignore new content to directories.
+!**/gce_workload_cert_refresh/
+!**/google_authorized_keys/
+!**/google_guest_agent/
+!**/google_metadata_script_runner/
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/gce_workload_cert_refresh/main.go 
new/guest-agent-20231031.01/gce_workload_cert_refresh/main.go
--- old/guest-agent-20231016.00/gce_workload_cert_refresh/main.go       
2023-10-16 20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/gce_workload_cert_refresh/main.go       
2023-10-31 20:11:57.000000000 +0100
@@ -23,6 +23,8 @@
        "io"
        "os"
        "path"
+       "path/filepath"
+       "strings"
        "time"
 
        "github.com/GoogleCloudPlatform/guest-agent/metadata"
@@ -30,6 +32,12 @@
 )
 
 const (
+       // trustAnchorsKey endpoint contains a set of trusted certificates for 
peer X.509 certificate chain validation.
+       trustAnchorsKey = "instance/gce-workload-certificates/trust-anchors"
+       // workloadIdentitiesKey endpoint contains identities managed by the 
GCE control plane. This contains the X.509 certificate and the private key for 
the VM's trust domain.
+       workloadIdentitiesKey = 
"instance/gce-workload-certificates/workload-identities"
+       // configStatusKey contains status and any errors in the config values 
provided via the VM metadata.
+       configStatusKey = "instance/gce-workload-certificates/config-status"
        // enableWorkloadCertsKey is set to true as custom metadata to enable 
automatic provisioning of credentials.
        enableWorkloadCertsKey = 
"instance/attributes/enable-workload-certificate"
        // contentDirPrefix is used as prefx to create certificate directories 
on refresh as contentDirPrefix-<time>.
@@ -42,8 +50,10 @@
 
 var (
        // mdsClient is the client used to query Metadata server.
-       mdsClient   *metadata.Client
+       mdsClient   metadata.MDSClientInterface
        programName = path.Base(os.Args[0])
+       // timeNow returns current time, defining as variable allows the time 
to be stubbed during testing.
+       timeNow = func() string { return time.Now().Format(time.RFC3339) }
 )
 
 func init() {
@@ -79,125 +89,62 @@
 
 /*
 metadata key instance/gce-workload-certificates/workload-identities
+MANAGED_WORKLOAD_IDENTITY_SPIFFE is of the format:
+spiffe://POOL_ID.global.PROJECT_NUMBER.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID
 
-       {
-        "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
+{
+       "status": "OK", // Status of the response,
+       "workloadCredentials": { // Credentials for the VM's trust domains
+               "MANAGED_WORKLOAD_IDENTITY_SPIFFE": {
+                       "certificatePem": "-----BEGIN 
CERTIFICATE-----datahere-----END CERTIFICATE-----",
+                       "privateKeyPem": "-----BEGIN PRIVATE 
KEY-----datahere-----END PRIVATE KEY-----"
                }
-               wi.WorkloadCredentials[domain] = wc
        }
-
-       return nil
 }
+*/
 
 // WorkloadCredential represents Workload Credentials in metadata.
 type WorkloadCredential struct {
-       Metadata       Metadata
-       CertificatePem string
-       PrivateKeyPem  string
+       CertificatePem string `json:"certificatePem"`
+       PrivateKeyPem  string `json:"privateKeyPem"`
 }
 
-/*
-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
+// WorkloadIdentities represents Workload Identities in metadata.
+type WorkloadIdentities struct {
+       Status              string                        `json:"status"`
+       WorkloadCredentials map[string]WorkloadCredential 
`json:"workloadCredentials"`
 }
 
-// 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
-       }
+/*
+metadata key instance/gce-workload-certificates/trust-anchors
 
-       for domain, value := range rcs {
-               rc := RootCertificate{}
-               err := json.Unmarshal(value, &rc)
-               if err != nil {
-                       return err
-               }
-               wtrc.RootCertificates[domain] = rc
-       }
+{
+    "status":  "<status string>" // Status of the response,
+    "trustAnchors": {  // Trust bundle for the VM's trust domains
+        "PEER_SPIFFE_TRUST_DOMAIN_1": {
+            "trustAnchorsPem" : "<Trust bundle containing the X.509 roots 
certificates>",
+               },
+        "PEER_SPIFFE_TRUST_DOMAIN_2": {
+            "trustAnchorsPem" : "<Trust bundle containing the X.509 roots 
certificates>",
+        }
+    }
+}
+*/
 
-       return nil
+// TrustAnchor represents one or more certificates in an arbitrary order in 
the metadata.
+type TrustAnchor struct {
+       TrustAnchorsPem string `json:"trustAnchorsPem"`
 }
 
-// RootCertificate represents a Root Certificate in metadata
-type RootCertificate struct {
-       Metadata            Metadata
-       RootCertificatesPem string
+// WorkloadTrustedAnchors represents Workload Trusted Root Certs in metadata.
+type WorkloadTrustedAnchors struct {
+       Status       string                 `json:"status"`
+       TrustAnchors map[string]TrustAnchor `json:"trustAnchors"`
 }
 
-// Metadata represents Metadata in metadata
-type Metadata struct {
-       WorkloadCredsDirPath string
+// outputOpts is a struct for output directory name and symlink templates.
+type outputOpts struct {
+       contentDirPrefix, tempSymlinkPrefix, symlink string
 }
 
 func main() {
@@ -211,7 +158,12 @@
        }
 
        opts.Writers = []io.Writer{os.Stderr}
-       logger.Init(ctx, opts)
+
+       if err := logger.Init(ctx, opts); err != nil {
+               fmt.Printf("Error initializing logger: %v", err)
+               os.Exit(1)
+       }
+
        defer logger.Infof("Done")
 
        if !isEnabled(ctx) {
@@ -219,33 +171,89 @@
                return
        }
 
-       if err := refreshCreds(ctx); err != nil {
+       out := outputOpts{contentDirPrefix, tempSymlinkPrefix, symlink}
+       if err := refreshCreds(ctx, out); err != nil {
                logger.Fatalf("Error refreshCreds: %v", err.Error())
        }
 
 }
 
-func refreshCreds(ctx context.Context) error {
-       project, err := getMetadata(ctx, "project/project-id")
+// findDomain finds the anchor matching with the domain from spiffeID.
+// spiffeID is of the form -
+// 
spiffe://POOL_ID.global.PROJECT_NUMBER.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID
+// where domain is POOL_ID.global.PROJECT_NUMBER.workload.id.goog
+// anchors is a map of various domains and their corresponding trust PEMs.
+// However, if anchor map contains single entry it returns that without any 
check.
+func findDomain(anchors map[string]TrustAnchor, spiffeID string) (string, 
error) {
+       c := len(anchors)
+       for k := range anchors {
+               if c == 1 {
+                       return k, nil
+               }
+               if strings.Contains(spiffeID, k) {
+                       return k, nil
+               }
+       }
+
+       return "", fmt.Errorf("no matching trust anchor found")
+}
+
+// writeTrustAnchors parses the input data, finds the domain from spiffeID and 
writes ca_certificate.pem
+// in the destDir for that domain.
+func writeTrustAnchors(wtrcsMd []byte, destDir, spiffeID string) error {
+       wtrcs := WorkloadTrustedAnchors{}
+       if err := json.Unmarshal(wtrcsMd, &wtrcs); err != nil {
+               return fmt.Errorf("error unmarshaling workload trusted root 
certs: %v", err)
+       }
+
+       // Currently there's only one trust anchor but there could be multipe 
trust anchors in future.
+       // In either case we want the trust anchor with domain matching with 
the one in SPIFFE ID.
+       domain, err := findDomain(wtrcs.TrustAnchors, spiffeID)
        if err != nil {
-               return fmt.Errorf("error getting project ID: %v", err)
+               return err
+       }
+
+       return os.WriteFile(fmt.Sprintf("%s/ca_certificates.pem", destDir), 
[]byte(wtrcs.TrustAnchors[domain].TrustAnchorsPem), 0644)
+}
+
+// writeWorkloadIdentities parses the input data, writes the certificates.pem, 
private_key.pem files in the
+// destDir, and returns the SPIFFE ID for which it wrote the certificates.
+func writeWorkloadIdentities(destDir string, wisMd []byte) (string, error) {
+       var spiffeID string
+       wis := WorkloadIdentities{}
+       if err := json.Unmarshal(wisMd, &wis); err != nil {
+               return "", fmt.Errorf("error unmarshaling workload identities 
response: %w", err)
+       }
+
+       // Its guaranteed to have single entry in workload credentials map.
+       for k := range wis.WorkloadCredentials {
+               spiffeID = k
+               break
+       }
+
+       if err := os.WriteFile(filepath.Join(destDir, "certificates.pem"), 
[]byte(wis.WorkloadCredentials[spiffeID].CertificatePem), 0644); err != nil {
+               return "", fmt.Errorf("error writing certificates.pem: %w", err)
+       }
+
+       if err := os.WriteFile(filepath.Join(destDir, "private_key.pem"), 
[]byte(wis.WorkloadCredentials[spiffeID].PrivateKeyPem), 0644); err != nil {
+               return "", fmt.Errorf("error writing private_key.pem: %w", err)
        }
+       return spiffeID, nil
+}
+
+func refreshCreds(ctx context.Context, opts outputOpts) error {
+       now := timeNow()
+       contentDir := fmt.Sprintf("%s-%s", opts.contentDirPrefix, now)
+       tempSymlink := fmt.Sprintf("%s-%s", opts.tempSymlinkPrefix, now)
 
        // Get status first so it can be written even when other endpoints are 
empty.
-       certConfigStatus, err := getMetadata(ctx, 
"instance/gce-workload-certificates/config-status")
+       certConfigStatus, err := getMetadata(ctx, configStatusKey)
        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 {
@@ -253,72 +261,59 @@
        }
 
        // Write config_status first even if remaining endpoints are empty.
-       if err := os.WriteFile(fmt.Sprintf("%s/config_status", contentDir), 
certConfigStatus, 0644); err != nil {
+       if err := os.WriteFile(filepath.Join(contentDir, "config_status"), 
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 always exists and contains the 
config_status to surface config errors to the VM.
-       if _, err := os.Stat(symlink); os.IsNotExist(err) {
+       if _, err := os.Stat(opts.symlink); os.IsNotExist(err) {
                logger.Infof("Creating new symlink %s", symlink)
 
-               if err := os.Symlink(contentDir, symlink); err != nil {
+               if err := os.Symlink(contentDir, opts.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")
+       wisMd, err := getMetadata(ctx, workloadIdentitiesKey)
        if err != nil {
                return fmt.Errorf("error getting workload-identities: %v", err)
        }
 
-       wtrcsMd, err := getMetadata(ctx, 
"instance/gce-workload-certificates/root-certs")
+       spiffeID, err := writeWorkloadIdentities(contentDir, wisMd)
        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)
+               return fmt.Errorf("failed to write workload identities with 
error: %w", 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)
+       wtrcsMd, err := getMetadata(ctx, trustAnchorsKey)
+       if err != nil {
+               return fmt.Errorf("error getting workload-trust-anchors: %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 := writeTrustAnchors(wtrcsMd, contentDir, spiffeID); err != nil {
+               return fmt.Errorf("failed to write trust anchors: %w", err)
        }
 
        if err := os.Symlink(contentDir, tempSymlink); err != nil {
                return fmt.Errorf("error creating temporary link: %v", err)
        }
 
-       oldTarget, err := os.Readlink(symlink)
+       oldTarget, err := os.Readlink(opts.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)
+       logger.Infof("Rotating symlink %s", opts.symlink)
 
-       if err := os.Rename(tempSymlink, symlink); err != nil {
+       if err := os.Rename(tempSymlink, opts.symlink); err != nil {
                return fmt.Errorf("error rotating target link: %v", err)
        }
 
        // Clean up previous contents dir.
-       newTarget, err := os.Readlink(symlink)
+       newTarget, err := os.Readlink(opts.symlink)
        if err != nil {
                return fmt.Errorf("error reading new symlink: %v, unable to 
remove old symlink target", err)
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/gce_workload_cert_refresh/main_test.go 
new/guest-agent-20231031.01/gce_workload_cert_refresh/main_test.go
--- old/guest-agent-20231016.00/gce_workload_cert_refresh/main_test.go  
1970-01-01 01:00:00.000000000 +0100
+++ new/guest-agent-20231031.01/gce_workload_cert_refresh/main_test.go  
2023-10-31 20:11:57.000000000 +0100
@@ -0,0 +1,495 @@
+// 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 main
+
+import (
+       "context"
+       "encoding/json"
+       "fmt"
+       "os"
+       "path/filepath"
+       "testing"
+
+       "github.com/GoogleCloudPlatform/guest-agent/metadata"
+       "github.com/google/go-cmp/cmp"
+)
+
+const (
+       workloadRespTpl = `
+       {
+               "status": "OK",
+               "workloadCredentials": {
+                       "%s": {
+                               "certificatePem": "%s",
+                               "privateKeyPem": "%s"
+                       }
+               }
+       }
+       `
+       trustAnchorRespTpl = `
+       {
+               "status": "Ok",
+               "trustAnchors": {
+                       "%s": {
+                               "trustAnchorsPem": "%s"
+                       },
+                       "%s": {
+                               "trustAnchorsPem": "%s"
+                       }
+               }
+       }
+       `
+       testConfigStatusResp = `
+       {
+               "status": "Ok",
+       }
+       `
+)
+
+func TestWorkloadIdentitiesUnmarshal(t *testing.T) {
+       certPem := "-----BEGIN CERTIFICATE-----datahere-----END 
CERTIFICATE-----"
+       pvtPem := "-----BEGIN PRIVATE KEY-----datahere-----END PRIVATE KEY-----"
+       spiffe := 
"spiffe://12345.global.67890.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID"
+
+       resp := fmt.Sprintf(workloadRespTpl, spiffe, certPem, pvtPem)
+       want := WorkloadIdentities{
+               Status: "OK",
+               WorkloadCredentials: map[string]WorkloadCredential{
+                       spiffe: {
+                               CertificatePem: certPem,
+                               PrivateKeyPem:  pvtPem,
+                       },
+               },
+       }
+
+       got := WorkloadIdentities{}
+       if err := json.Unmarshal([]byte(resp), &got); err != nil {
+               t.Errorf("WorkloadIdentities.UnmarshalJSON(%s) failed 
unexpectedly with error: %v", resp, err)
+       }
+
+       if diff := cmp.Diff(want, got); diff != "" {
+               t.Errorf("Workload identities diff (-want +got):\n%s", diff)
+       }
+}
+
+func TestTrustAnchorsUnmarshal(t *testing.T) {
+       domain1 := "12345.global.67890.workload.id.goog"
+       pem1 := "-----BEGIN CERTIFICATE-----datahere1-----END CERTIFICATE-----"
+       domain2 := "PEER_SPIFFE_TRUST_DOMAIN_2"
+       pem2 := "-----BEGIN CERTIFICATE-----datahere2-----END CERTIFICATE-----"
+
+       resp := fmt.Sprintf(trustAnchorRespTpl, domain1, pem1, domain2, pem2)
+       want := WorkloadTrustedAnchors{
+               Status: "Ok",
+               TrustAnchors: map[string]TrustAnchor{
+                       domain1: {
+                               TrustAnchorsPem: pem1,
+                       },
+                       domain2: {
+                               TrustAnchorsPem: pem2,
+                       },
+               },
+       }
+
+       got := WorkloadTrustedAnchors{}
+       if err := json.Unmarshal([]byte(resp), &got); err != nil {
+               t.Errorf("WorkloadTrustedRootCerts.UnmarshalJSON(%s) failed 
unexpectedly with error: %v", resp, err)
+       }
+
+       if diff := cmp.Diff(want, got); diff != "" {
+               t.Errorf("Workload trusted anchors diff (-want +got):\n%s", 
diff)
+       }
+}
+
+func TestWriteTrustAnchors(t *testing.T) {
+       spiffe := 
"spiffe://12345.global.67890.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID"
+       domain1 := "12345.global.67890.workload.id.goog"
+       pem1 := "-----BEGIN CERTIFICATE-----datahere1-----END CERTIFICATE-----"
+       domain2 := "PEER_SPIFFE_TRUST_DOMAIN_2"
+       pem2 := "-----BEGIN CERTIFICATE-----datahere2-----END CERTIFICATE-----"
+
+       resp := fmt.Sprintf(trustAnchorRespTpl, domain1, pem1, domain2, pem2)
+       dir := t.TempDir()
+       if err := writeTrustAnchors([]byte(resp), dir, spiffe); err != nil {
+               t.Errorf("writeTrustAnchors(%s,%s,%s) failed unexpectedly with 
error %v", resp, dir, spiffe, err)
+       }
+
+       got, err := os.ReadFile(filepath.Join(dir, "ca_certificates.pem"))
+       if err != nil {
+               t.Errorf("failed to read file at %s with error: %v", 
filepath.Join(dir, "ca_certificates.pem"), err)
+       }
+       if string(got) != pem1 {
+               t.Errorf("writeTrustAnchors(%s,%s,%s) wrote %q, expected to 
write %q", resp, dir, spiffe, string(got), pem1)
+       }
+}
+
+func TestWriteWorkloadIdentities(t *testing.T) {
+       certPem := "-----BEGIN CERTIFICATE-----datahere-----END 
CERTIFICATE-----"
+       pvtPem := "-----BEGIN PRIVATE KEY-----datahere-----END PRIVATE KEY-----"
+       spiffe := 
"spiffe://12345.global.67890.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID"
+
+       resp := fmt.Sprintf(workloadRespTpl, spiffe, certPem, pvtPem)
+       dir := t.TempDir()
+
+       gotID, err := writeWorkloadIdentities(dir, []byte(resp))
+       if err != nil {
+               t.Errorf("writeWorkloadIdentities(%s,%s) failed unexpectedly 
with error %v", dir, resp, err)
+       }
+       if gotID != spiffe {
+               t.Errorf("writeWorkloadIdentities(%s,%s) = %s, want %s", dir, 
resp, gotID, spiffe)
+       }
+
+       gotCertPem, err := os.ReadFile(filepath.Join(dir, "certificates.pem"))
+       if err != nil {
+               t.Errorf("failed to read file at %s with error: %v", 
filepath.Join(dir, "certificates.pem"), err)
+       }
+       if string(gotCertPem) != certPem {
+               t.Errorf("writeWorkloadIdentities(%s,%s) wrote %q, expected to 
write %q", dir, resp, string(gotCertPem), certPem)
+       }
+
+       gotPvtPem, err := os.ReadFile(filepath.Join(dir, "private_key.pem"))
+       if err != nil {
+               t.Errorf("failed to read file at %s with error: %v", 
filepath.Join(dir, "private_key.pem"), err)
+       }
+       if string(gotPvtPem) != pvtPem {
+               t.Errorf("writeWorkloadIdentities(%s,%s) wrote %q, expected to 
write %q", dir, resp, string(gotPvtPem), pvtPem)
+       }
+}
+
+func TestFindDomainError(t *testing.T) {
+       anchors := map[string]TrustAnchor{
+               "67890.global.12345.workload.id.goog": {},
+               "55555.global.67890.workload.id.goog": {},
+       }
+       spiffeID := 
"spiffe://12345.global.67890.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID"
+
+       if _, err := findDomain(anchors, spiffeID); err == nil {
+               t.Errorf("findDomain(%+v, %s) succeded for unknown anchors, 
want error", anchors, spiffeID)
+       }
+}
+
+func TestFindDomain(t *testing.T) {
+       tests := []struct {
+               desc     string
+               anchors  map[string]TrustAnchor
+               spiffeID string
+               want     string
+       }{
+               {
+                       desc:     "single_trust_anchor",
+                       anchors:  
map[string]TrustAnchor{"12345.global.67890.workload.id.goog": {}},
+                       spiffeID: 
"spiffe://12345.global.67890.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID",
+                       want:     "12345.global.67890.workload.id.goog",
+               },
+               {
+                       desc: "multiple_trust_anchor",
+                       anchors: map[string]TrustAnchor{
+                               "67890.global.12345.workload.id.goog": {},
+                               "12345.global.67890.workload.id.goog": {},
+                       },
+                       spiffeID: 
"spiffe://12345.global.67890.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID",
+                       want:     "12345.global.67890.workload.id.goog",
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.desc, func(t *testing.T) {
+                       got, err := findDomain(test.anchors, test.spiffeID)
+                       if err != nil {
+                               t.Errorf("findDomain(%+v, %s) failed 
unexpectedly with error: %v", test.anchors, test.spiffeID, err)
+                       }
+                       if got != test.want {
+                               t.Errorf("findDomain(%+v, %s) = %s, want %s", 
test.anchors, test.spiffeID, got, test.want)
+                       }
+               })
+       }
+}
+
+func TestIsEnabled(t *testing.T) {
+       ctx := context.Background()
+
+       tests := []struct {
+               desc    string
+               enabled string
+               want    bool
+               err     string
+       }{
+               {
+                       desc:    "attr_correctly_added",
+                       enabled: "true",
+                       want:    true,
+               },
+               {
+                       desc:    "attr_incorrectly_added",
+                       enabled: "blaah",
+                       want:    false,
+               },
+               {
+                       desc: "attr_not_added",
+                       want: false,
+                       err:  enableWorkloadCertsKey,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.desc, func(t *testing.T) {
+                       mdsClient = &mdsTestClient{enabled: test.enabled, 
throwErrOn: test.err}
+                       if got := isEnabled(ctx); got != test.want {
+                               t.Errorf("isEnabled(ctx) = %t, want %t", got, 
test.want)
+                       }
+               })
+       }
+}
+
+// mdsTestClient is fake client to stub MDS response in unit tests.
+type mdsTestClient struct {
+       // Is credential generation enabled.
+       enabled string
+       // Workload template.
+       spiffe, certPem, pvtPem string
+       // Trust Anchor template.
+       domain1, pem1, domain2, pem2 string
+       // Throw error on MDS request for "key".
+       throwErrOn string
+}
+
+func (mds *mdsTestClient) Get(ctx context.Context) (*metadata.Descriptor, 
error) {
+       return nil, fmt.Errorf("Get() not yet implemented")
+}
+
+func (mds *mdsTestClient) GetKey(ctx context.Context, key string, headers 
map[string]string) (string, error) {
+       if mds.throwErrOn == key {
+               return "", fmt.Errorf("this is fake error for testing")
+       }
+
+       switch key {
+       case enableWorkloadCertsKey:
+               return mds.enabled, nil
+       case configStatusKey:
+               return testConfigStatusResp, nil
+       case workloadIdentitiesKey:
+               return fmt.Sprintf(workloadRespTpl, mds.spiffe, mds.certPem, 
mds.pvtPem), nil
+       case trustAnchorsKey:
+               return fmt.Sprintf(trustAnchorRespTpl, mds.domain1, mds.pem1, 
mds.domain2, mds.pem2), nil
+       default:
+               return "", fmt.Errorf("unknown key %q", key)
+       }
+}
+
+func (mds *mdsTestClient) GetKeyRecursive(ctx context.Context, key string) 
(string, error) {
+       return "", fmt.Errorf("GetKeyRecursive() not yet implemented")
+}
+
+func (mds *mdsTestClient) Watch(ctx context.Context) (*metadata.Descriptor, 
error) {
+       return nil, fmt.Errorf("Watch() not yet implemented")
+}
+
+func (mds *mdsTestClient) WriteGuestAttributes(ctx context.Context, key 
string, value string) error {
+       return fmt.Errorf("WriteGuestattributes() not yet implemented")
+}
+
+func TestRefreshCreds(t *testing.T) {
+       ctx := context.Background()
+       tmp := t.TempDir()
+
+       // Templates to use in iterations.
+       spiffeTpl := 
"spiffe://12345.global.67890.workload.id.goog.%d/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID"
+       domain1Tpl := "12345.global.67890.workload.id.goog.%d"
+       pem1Tpl := "-----BEGIN CERTIFICATE-----datahere1.%d-----END 
CERTIFICATE-----"
+       domain2 := "PEER_SPIFFE_TRUST_DOMAIN_2_IGNORE"
+       pem2Tpl := "-----BEGIN CERTIFICATE-----datahere2.%d-----END 
CERTIFICATE-----"
+       certPemTpl := "-----BEGIN CERTIFICATE-----datahere.%d-----END 
CERTIFICATE-----"
+       pvtPemTpl := "-----BEGIN PRIVATE KEY-----datahere.%d-----END PRIVATE 
KEY-----"
+
+       contentPrefix := filepath.Join(tmp, "workload-spiffe-contents")
+       tmpSymlinkPrefix := filepath.Join(tmp, "workload-spiffe-symlink")
+       link := filepath.Join(tmp, "workload-spiffe-credentials")
+       out := outputOpts{contentPrefix, tmpSymlinkPrefix, link}
+
+       // Run refresh creds thrice to test updates.
+       // Link (workload-spiffe-credentials) should always refer to the 
updated content
+       // and previous directories should be removed.
+       for i := 1; i <= 3; i++ {
+               timeNow = func() string { return fmt.Sprintf("%d", i) }
+               spiffe := fmt.Sprintf(spiffeTpl, i)
+               domain1 := fmt.Sprintf(domain1Tpl, i)
+               pem1 := fmt.Sprintf(pem1Tpl, i)
+               pem2 := fmt.Sprintf(pem2Tpl, i)
+               certPem := fmt.Sprintf(certPemTpl, i)
+               pvtPem := fmt.Sprintf(pvtPemTpl, i)
+
+               mdsClient = &mdsTestClient{
+                       spiffe:  spiffe,
+                       certPem: certPem,
+                       pvtPem:  pvtPem,
+                       domain1: domain1,
+                       pem1:    pem1,
+                       domain2: domain2,
+                       pem2:    pem2,
+               }
+
+               if err := refreshCreds(ctx, out); err != nil {
+                       t.Errorf("refreshCreds(ctx, %+v) failed unexpectedly 
with error: %v", out, err)
+               }
+
+               // Verify all files are created with the content as expected.
+               tests := []struct {
+                       path    string
+                       content string
+               }{
+                       {
+                               path:    filepath.Join(link, 
"ca_certificates.pem"),
+                               content: pem1,
+                       },
+                       {
+                               path:    filepath.Join(link, 
"certificates.pem"),
+                               content: certPem,
+                       },
+                       {
+                               path:    filepath.Join(link, "private_key.pem"),
+                               content: pvtPem,
+                       },
+                       {
+                               path:    filepath.Join(link, "config_status"),
+                               content: testConfigStatusResp,
+                       },
+               }
+
+               for _, test := range tests {
+                       t.Run(test.path, func(t *testing.T) {
+                               got, err := os.ReadFile(test.path)
+                               if err != nil {
+                                       t.Errorf("failed to read expected file 
%q and content %q with error: %v", test.path, test.content, err)
+                               }
+                               if string(got) != test.content {
+                                       t.Errorf("refreshCreds(ctx, %+v) wrote 
%q, want content %q", out, string(got), test.content)
+                               }
+                       })
+               }
+
+               // Verify the symlink was created and references the right 
destination directory.
+               want := fmt.Sprintf("%s-%d", contentPrefix, i)
+               got, err := os.Readlink(link)
+               if err != nil {
+                       t.Errorf("os.Readlink(%s) failed unexpectedly with 
error %v", link, err)
+               }
+               if got != want {
+                       t.Errorf("os.Readlink(%s) = %s, want %s", link, got, 
want)
+               }
+
+               // If its not first run make sure prev creds are deleted.
+               if i > 1 {
+                       prevDir := fmt.Sprintf("%s-%d", contentPrefix, i-1)
+                       if _, err := os.Stat(prevDir); err == nil {
+                               t.Errorf("os.Stat(%s) succeeded on prev content 
directory, want error", prevDir)
+                       }
+               }
+       }
+}
+
+func TestRefreshCredsError(t *testing.T) {
+       ctx := context.Background()
+       tmp := t.TempDir()
+
+       // Templates to use in iterations.
+       spiffe := 
"spiffe://12345.global.67890.workload.id.goog/ns/NAMESPACE_ID/sa/MANAGED_IDENTITY_ID"
+       domain1 := "12345.global.67890.workload.id.goog"
+       pem1 := "-----BEGIN CERTIFICATE-----datahere1-----END CERTIFICATE-----"
+       domain2 := "PEER_SPIFFE_TRUST_DOMAIN_2_IGNORE"
+       pem2 := "-----BEGIN CERTIFICATE-----datahere2-----END CERTIFICATE-----"
+       certPem := "-----BEGIN CERTIFICATE-----datahere-----END 
CERTIFICATE-----"
+       pvtPem := "-----BEGIN PRIVATE KEY-----datahere-----END PRIVATE KEY-----"
+
+       contentPrefix := filepath.Join(tmp, "workload-spiffe-contents")
+       tmpSymlinkPrefix := filepath.Join(tmp, "workload-spiffe-symlink")
+       link := filepath.Join(tmp, "workload-spiffe-credentials")
+       out := outputOpts{contentPrefix, tmpSymlinkPrefix, link}
+
+       client := &mdsTestClient{
+               spiffe:  spiffe,
+               certPem: certPem,
+               pvtPem:  pvtPem,
+               domain1: domain1,
+               pem1:    pem1,
+               domain2: domain2,
+               pem2:    pem2,
+       }
+
+       mdsClient = client
+
+       // Run refresh creds twice. First run would succeed and second would 
fail. Verify all
+       // creds generated on the first run are present as is after failed 
second run.
+       for i := 1; i <= 2; i++ {
+               timeNow = func() string { return fmt.Sprintf("%d", i) }
+
+               if i == 1 {
+                       // First run should succeed.
+                       if err := refreshCreds(ctx, out); err != nil {
+                               t.Errorf("refreshCreds(ctx, %+v) failed 
unexpectedly with error: %v", out, err)
+                       }
+               } else if i == 2 {
+                       // Second run should fail. Fail in getting last 
metadata entry.
+                       client.throwErrOn = trustAnchorsKey
+                       if err := refreshCreds(ctx, out); err == nil {
+                               t.Errorf("refreshCreds(ctx, %+v) succeeded for 
fake metadata error, should've failed", out)
+                       }
+               }
+
+               // Verify all files are created and are still present with the 
content as expected.
+               tests := []struct {
+                       path    string
+                       content string
+               }{
+                       {
+                               path:    filepath.Join(link, 
"ca_certificates.pem"),
+                               content: pem1,
+                       },
+                       {
+                               path:    filepath.Join(link, 
"certificates.pem"),
+                               content: certPem,
+                       },
+                       {
+                               path:    filepath.Join(link, "private_key.pem"),
+                               content: pvtPem,
+                       },
+                       {
+                               path:    filepath.Join(link, "config_status"),
+                               content: testConfigStatusResp,
+                       },
+               }
+
+               for _, test := range tests {
+                       t.Run(test.path, func(t *testing.T) {
+                               got, err := os.ReadFile(test.path)
+                               if err != nil {
+                                       t.Errorf("failed to read expected file 
%q and content %q with error: %v", test.path, test.content, err)
+                               }
+                               if string(got) != test.content {
+                                       t.Errorf("refreshCreds(ctx, %+v) wrote 
%q, want content %q", out, string(got), test.content)
+                               }
+                       })
+               }
+
+               // Verify the symlink was created and references the same 
destination directory.
+               want := fmt.Sprintf("%s-%d", contentPrefix, 1)
+               got, err := os.Readlink(link)
+               if err != nil {
+                       t.Errorf("os.Readlink(%s) failed unexpectedly with 
error %v", link, err)
+               }
+               if got != want {
+                       t.Errorf("os.Readlink(%s) = %s, want %s", link, got, 
want)
+               }
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/guest-agent-20231016.00/go.mod 
new/guest-agent-20231031.01/go.mod
--- old/guest-agent-20231016.00/go.mod  2023-10-16 20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/go.mod  2023-10-31 20:11:57.000000000 +0100
@@ -10,6 +10,7 @@
        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-cmp v0.5.9
        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
@@ -30,7 +31,6 @@
        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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/google_guest_agent/agentcrypto/mtls_mds_windows.go 
new/guest-agent-20231031.01/google_guest_agent/agentcrypto/mtls_mds_windows.go
--- 
old/guest-agent-20231016.00/google_guest_agent/agentcrypto/mtls_mds_windows.go  
    2023-10-16 20:47:54.000000000 +0200
+++ 
new/guest-agent-20231031.01/google_guest_agent/agentcrypto/mtls_mds_windows.go  
    2023-10-31 20:11:57.000000000 +0100
@@ -59,6 +59,12 @@
 
 // writeRootCACert writes Root CA cert from UEFI variable to output file.
 func (j *CredsJob) writeRootCACert(_ context.Context, cacert []byte, 
outputFile string) error {
+       // Try to fetch previous certificate's serial number before it gets 
overwritten.
+       num, err := serialNumber(outputFile)
+       if err != nil {
+               logger.Debugf("No previous MDS root certificate was found, will 
skip cleanup: %v", err)
+       }
+
        if err := utils.SaferWriteFile(cacert, outputFile, 0644); err != nil {
                return err
        }
@@ -84,6 +90,25 @@
                return fmt.Errorf("failed to store root cert ctx in store: %w", 
err)
        }
 
+       // MDS root cert was not refreshed or there's no previous cert, nothing 
to do, return.
+       if num == "" || fmt.Sprintf("%x", x509Cert.SerialNumber) == num {
+               return nil
+       }
+
+       // Certificate is refreshed. Best effort to find the certcontext and 
delete it.
+       // Don't throw error here, it would skip client credential generation 
which
+       // may be about to expire.
+       oldCtx, err := findCert(root, certificateIssuer, num)
+       if err != nil {
+               logger.Warningf("Failed to find previous MDS root certificate 
with error: %v", err)
+               return nil
+       }
+
+       if err := deleteCert(oldCtx, root); err != nil {
+               logger.Warningf("Failed to delete previous MDS root 
certificate(%s) with error: %v", num, err)
+               return nil
+       }
+
        return nil
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/google_guest_agent/cfg/cfg.go 
new/guest-agent-20231031.01/google_guest_agent/cfg/cfg.go
--- old/guest-agent-20231016.00/google_guest_agent/cfg/cfg.go   2023-10-16 
20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/google_guest_agent/cfg/cfg.go   2023-10-31 
20:11:57.000000000 +0100
@@ -27,6 +27,10 @@
        // should always return it.
        instance *Sections
 
+       // configFile is a pointer to a function which takes the current OS 
name and returns
+       // an appropriate config file name. Replaceable by unit tests.
+       configFile = defaultConfigFile
+
        // 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
@@ -87,6 +91,9 @@
 [OSLogin]
 cert_authentication = true
 
+[MDS]
+mtls_bootstrapping_enabled = true
+
 [Snapshots]
 enabled = false
 snapshot_service_ip = 169.254.169.254
@@ -94,7 +101,6 @@
 timeout_in_seconds = 60
 
 [Unstable]
-mds_mtls = false
 `
 )
 
@@ -144,6 +150,9 @@
        // OSLogin defines the OS Login configuration options.
        OSLogin *OSLogin `ini:"OSLogin,omitempty"`
 
+       // MDS defines the MDS configuration options.
+       MDS *MDS `ini:"MDS,omitempty"`
+
        // Snpashots defines the snapshot listener configuration and behavior 
i.e. the server address and port.
        Snapshots *Snapshots `ini:"Snapshots,omitempty"`
 
@@ -237,6 +246,12 @@
        CertAuthentication bool `ini:"cert_authentication,omitempty"`
 }
 
+// MDS contains the configurations for MDS section.
+type MDS struct {
+       // MTLSBootstrappingEnabled enables/disables the mTLS credential 
refresher.
+       MTLSBootstrappingEnabled bool 
`ini:"mtls_bootstrapping_enabled,omitempty"`
+}
+
 // NetworkInterfaces contains the configurations of NetworkInterfaces section.
 type NetworkInterfaces struct {
        DHCPCommand  string `ini:"dhcp_command,omitempty"`
@@ -256,7 +271,6 @@
 // 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.
@@ -274,18 +288,17 @@
 }
 
 func defaultDataSources(extraDefaults []byte) []interface{} {
-       var res []interface{}
-       configFile := defaultConfigFile(runtime.GOOS)
+       var res = []interface{}{[]byte(defaultConfig)}
+       config := configFile(runtime.GOOS)
 
        if len(extraDefaults) > 0 {
                res = append(res, extraDefaults)
        }
 
        return append(res, []interface{}{
-               []byte(defaultConfig),
-               configFile,
-               configFile + ".distro",
-               configFile + ".template",
+               config + ".distro",
+               config + ".template",
+               config,
        }...)
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/google_guest_agent/cfg/cfg_test.go 
new/guest-agent-20231031.01/google_guest_agent/cfg/cfg_test.go
--- old/guest-agent-20231016.00/google_guest_agent/cfg/cfg_test.go      
2023-10-16 20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/google_guest_agent/cfg/cfg_test.go      
2023-10-31 20:11:57.000000000 +0100
@@ -14,7 +14,11 @@
 
 package cfg
 
-import "testing"
+import (
+       "os"
+       "path"
+       "testing"
+)
 
 func TestLoad(t *testing.T) {
        if err := Load(nil); err != nil {
@@ -92,3 +96,82 @@
                t.Errorf("Get() should return always the same pointer, 
expected: %p, got: %p", firstCfg, secondCfg)
        }
 }
+
+func TestConfigLoadOrder(t *testing.T) {
+       config := path.Join(t.TempDir(), "config.cfg")
+       configFile = func(string) string { return config }
+       t.Cleanup(func() { configFile = defaultConfigFile })
+       testcases := []struct {
+               name           string
+               extraDefault   string
+               distroConfig   string
+               templateConfig string
+               userConfig     string
+               output         bool
+       }{
+               {
+                       name:           "user config override",
+                       extraDefault:   "[NetworkInterfaces]\nSetup = true\n",
+                       distroConfig:   "[NetworkInterfaces]\nSetup = true\n",
+                       templateConfig: "[NetworkInterfaces]\nSetup = true\n",
+                       userConfig:     "[NetworkInterfaces]\nSetup = false\n",
+                       output:         false,
+               },
+               {
+                       name:           "template config override",
+                       extraDefault:   "[NetworkInterfaces]\nSetup = true\n",
+                       distroConfig:   "[NetworkInterfaces]\nSetup = true\n",
+                       templateConfig: "[NetworkInterfaces]\nSetup = false\n",
+                       userConfig:     "",
+                       output:         false,
+               },
+               {
+                       name:           "distro config override",
+                       extraDefault:   "[NetworkInterfaces]\nSetup = true\n",
+                       distroConfig:   "[NetworkInterfaces]\nSetup = false\n",
+                       templateConfig: "",
+                       userConfig:     "",
+                       output:         false,
+               },
+               {
+                       name:           "extra default override",
+                       extraDefault:   "[NetworkInterfaces]\nSetup = false\n",
+                       distroConfig:   "",
+                       templateConfig: "",
+                       userConfig:     "",
+                       output:         false,
+               },
+               {
+                       // If this fails, other test case results are not valid
+                       name:           "default is true",
+                       extraDefault:   "",
+                       distroConfig:   "",
+                       templateConfig: "",
+                       userConfig:     "",
+                       output:         true,
+               },
+       }
+       for _, tc := range testcases {
+               t.Run(tc.name, func(t *testing.T) {
+                       err := os.WriteFile(config+".distro", 
[]byte(tc.distroConfig), 0777)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       err = os.WriteFile(config+".template", 
[]byte(tc.templateConfig), 0777)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       err = os.WriteFile(config, []byte(tc.userConfig), 0777)
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       err = Load([]byte(tc.extraDefault))
+                       if err != nil {
+                               t.Fatal(err)
+                       }
+                       if Get().NetworkInterfaces.Setup != tc.output {
+                               t.Errorf("unexpected config value for 
NetworkInterfaces.Setup, wanted %v but got %v", Get().NetworkInterfaces.Setup, 
tc.output)
+                       }
+               })
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/google_guest_agent/instance_setup.go 
new/guest-agent-20231031.01/google_guest_agent/instance_setup.go
--- old/guest-agent-20231016.00/google_guest_agent/instance_setup.go    
2023-10-16 20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/google_guest_agent/instance_setup.go    
2023-10-31 20:11:57.000000000 +0100
@@ -212,7 +212,7 @@
        // use them. Processes may depend on the Guest Agent at startup to 
ensure that the credentials are
        // available for use. By generating the credentials before notifying 
the systemd, we ensure that
        // they are generated for any process that depends on the Guest Agent.
-       if config.Unstable.MDSMTLS {
+       if config.MDS.MTLSBootstrappingEnabled {
                scheduler.ScheduleJobs(ctx, []scheduler.Job{agentcrypto.New()}, 
true)
        }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/guest-agent-20231016.00/google_guest_agent/main.go 
new/guest-agent-20231031.01/google_guest_agent/main.go
--- old/guest-agent-20231016.00/google_guest_agent/main.go      2023-10-16 
20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/google_guest_agent/main.go      2023-10-31 
20:11:57.000000000 +0100
@@ -25,7 +25,6 @@
        "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"
@@ -179,7 +178,7 @@
        // 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.
+               // 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)
@@ -199,11 +198,6 @@
        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)
-       }
-
        eventManager := events.Get()
        if err := eventManager.AddDefaultWatchers(ctx); err != nil {
                logger.Errorf("Error initializing event manager: %v", err)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/google_guest_agent/scheduler/logger.go 
new/guest-agent-20231031.01/google_guest_agent/scheduler/logger.go
--- old/guest-agent-20231016.00/google_guest_agent/scheduler/logger.go  
2023-10-16 20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/google_guest_agent/scheduler/logger.go  
2023-10-31 20:11:57.000000000 +0100
@@ -21,9 +21,9 @@
 type cronLogger struct{}
 
 func (cl *cronLogger) Info(msg string, keysAndValues ...any) {
-       logger.Infof("%s: %+v", msg, keysAndValues)
+       logger.Infof("Scheduler - %s: %+v", msg, keysAndValues)
 }
 
 func (cl *cronLogger) Error(err error, msg string, keysAndValues ...any) {
-       logger.Infof("%s: %+v", msg, keysAndValues)
+       logger.Infof("Scheduler - %s: %+v", msg, keysAndValues)
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/google_guest_agent/scheduler/scheduler.go 
new/guest-agent-20231031.01/google_guest_agent/scheduler/scheduler.go
--- old/guest-agent-20231016.00/google_guest_agent/scheduler/scheduler.go       
2023-10-16 20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/google_guest_agent/scheduler/scheduler.go       
2023-10-31 20:11:57.000000000 +0100
@@ -69,6 +69,7 @@
 // getFunc generates a wrapper function for cron scheduler.
 func (s *Scheduler) getFunc(ctx context.Context, job Job) func() {
        f := func() {
+               logger.Infof("Invoking job %q", job.ID())
                schedule, err := job.Run(ctx)
                if !schedule {
                        s.UnscheduleJob(job.ID())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/guest-agent-20231016.00/google_guest_agent/snapshot_listener.go 
new/guest-agent-20231031.01/google_guest_agent/snapshot_listener.go
--- old/guest-agent-20231016.00/google_guest_agent/snapshot_listener.go 
2023-10-16 20:47:54.000000000 +0200
+++ new/guest-agent-20231031.01/google_guest_agent/snapshot_listener.go 
2023-10-31 20:11:57.000000000 +0100
@@ -16,6 +16,7 @@
 
 import (
        "context"
+       "errors"
        "fmt"
        "os"
        "time"
@@ -65,7 +66,7 @@
 }
 
 func listenForSnapshotRequests(ctx context.Context, address string, 
requestChan chan<- *sspb.GuestMessage) {
-       for {
+       for leaving := false; !leaving; {
                // 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())
@@ -82,6 +83,7 @@
                r, err := c.CreateConnection(ctx, &guestReady)
                if err != nil {
                        logger.Errorf("Error creating connection: %v.", err)
+                       leaving = errors.Is(err, context.Canceled)
                        cancel()
                        continue
                }

++++++ vendor.tar.gz ++++++

Reply via email to