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 ++++++