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 2022-09-17 20:10:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/google-guest-agent (Old) and /work/SRC/openSUSE:Factory/.google-guest-agent.new.2083 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "google-guest-agent" Sat Sep 17 20:10:38 2022 rev:15 rq:1004182 version:20220824.00 Changes: -------- --- /work/SRC/openSUSE:Factory/google-guest-agent/google-guest-agent.changes 2022-08-16 17:07:10.539776179 +0200 +++ /work/SRC/openSUSE:Factory/.google-guest-agent.new.2083/google-guest-agent.changes 2022-09-17 20:10:50.637244094 +0200 @@ -1,0 +2,10 @@ +Fri Sep 16 15:27:23 UTC 2022 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to version 20220824.00 + * Workload certs (#177) +- from version 20220823.00 + * add members to OWNERS (#178) + * Expired key tests (#176) + * correct expired key handling (#175) + +------------------------------------------------------------------- Old: ---- guest-agent-20220713.00.tar.gz New: ---- guest-agent-20220824.00.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ google-guest-agent.spec ++++++ --- /var/tmp/diff_new_pack.x2SqGF/_old 2022-09-17 20:10:51.457246459 +0200 +++ /var/tmp/diff_new_pack.x2SqGF/_new 2022-09-17 20:10:51.457246459 +0200 @@ -24,7 +24,7 @@ %global import_path %{provider_prefix} Name: google-guest-agent -Version: 20220713.00 +Version: 20220824.00 Release: 0 Summary: Google Cloud Guest Agent License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.x2SqGF/_old 2022-09-17 20:10:51.493246563 +0200 +++ /var/tmp/diff_new_pack.x2SqGF/_new 2022-09-17 20:10:51.493246563 +0200 @@ -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">20220713.00</param> - <param name="revision">20220713.00</param> + <param name="versionformat">20220824.00</param> + <param name="revision">20220824.00</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-20220713.00.tar.gz</param> + <param name="archive">guest-agent-20220824.00.tar.gz</param> </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.x2SqGF/_old 2022-09-17 20:10:51.517246632 +0200 +++ /var/tmp/diff_new_pack.x2SqGF/_new 2022-09-17 20:10:51.525246656 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/GoogleCloudPlatform/guest-agent/</param> - <param name="changesrevision">792fce795218633bcbde505fb3457a0b24f26d37</param></service></servicedata> + <param name="changesrevision">1bdde681dd3b700159392eb87efbef5c1bb5515c</param></service></servicedata> (No newline at EOF) ++++++ guest-agent-20220713.00.tar.gz -> guest-agent-20220824.00.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20220713.00/OWNERS new/guest-agent-20220824.00/OWNERS --- old/guest-agent-20220713.00/OWNERS 2022-06-22 23:00:27.000000000 +0200 +++ new/guest-agent-20220824.00/OWNERS 2022-08-24 03:13:05.000000000 +0200 @@ -6,8 +6,14 @@ - hopkiw - zmarano - bkatyl + - dorileo + - jjerger + - koln67 reviewers: - adjackura - hopkiw - zmarano - bkatyl + - dorileo + - jjerger + - koln67 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20220713.00/gce_workload_certs_refresh/main.go new/guest-agent-20220824.00/gce_workload_certs_refresh/main.go --- old/guest-agent-20220713.00/gce_workload_certs_refresh/main.go 1970-01-01 01:00:00.000000000 +0100 +++ new/guest-agent-20220824.00/gce_workload_certs_refresh/main.go 2022-08-24 03:13:05.000000000 +0200 @@ -0,0 +1,289 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// GoogleAuthorizedKeys obtains SSH keys from metadata. +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "time" + + "github.com/GoogleCloudPlatform/guest-logging-go/logger" +) + +const ( + contentDirPrefix = "/run/secrets/workload-spiffe-contents" + tempSymlinkPrefix = "/run/secrets/workload-spiffe-symlink" + symlink = "/run/secrets/workload-spiffe-credentials" +) + +var ( + programName = "gce_workload_certs_refresh" + version string + metadataURL = "http://169.254.169.254/computeMetadata/v1/" + defaultTimeout = 2 * time.Second +) + +func logFormat(e logger.LogEntry) string { + now := time.Now().Format("2006/01/02 15:04:05") + return fmt.Sprintf("%s: %s", now, e.Message) +} + +func getMetadata(key string) ([]byte, error) { + client := &http.Client{ + Timeout: defaultTimeout, + } + + url := metadataURL + key + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Add("Metadata-Flavor", "Google") + + var res *http.Response + + // Retry up to 5 times + for i := 1; i < 6; i++ { + res, err = client.Do(req) + if err == nil { + break + } + logger.Errorf("error connecting to metadata server, retrying in 3s, error: %v", err) + time.Sleep(time.Duration(3) * time.Second) + } + if err != nil { + return nil, err + } + defer res.Body.Close() + + md, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return md, nil +} + +/* +metadata key instance/workload-identities + + { + "status": "OK", + "workloadCredentials": { + "PROJECT.svc.id.goog": { + "metadata": { + "workload_creds_dir_path": "/var/run/secrets/workload-spiffe-credentials" + }, + "certificatePem": "-----BEGIN CERTIFICATE-----datahere-----END CERTIFICATE-----", + "privateKeyPem": "-----BEGIN PRIVATE KEY-----datahere-----END PRIVATE KEY-----" + } + } + } +*/ + +// WorkloadIdentities represents Workload Identities in metadata. +type WorkloadIdentities struct { + Status string + WorkloadCredentials map[string]WorkloadCredential +} + +// UnmarshalJSON is a custom JSON unmarshaller for WorkloadIdentities. +func (wi *WorkloadIdentities) UnmarshalJSON(b []byte) error { + tmp := map[string]json.RawMessage{} + err := json.Unmarshal(b, &tmp) + if err != nil { + return err + } + + if err := json.Unmarshal(tmp["status"], &wi.Status); err != nil { + return err + } + + wi.WorkloadCredentials = map[string]WorkloadCredential{} + wcs := map[string]json.RawMessage{} + if err := json.Unmarshal(tmp["workloadCredentials"], &wcs); err != nil { + return err + } + + for domain, value := range wcs { + wc := WorkloadCredential{} + err := json.Unmarshal(value, &wc) + if err != nil { + return err + } + wi.WorkloadCredentials[domain] = wc + } + + return nil +} + +// WorkloadCredential represents Workload Credentials in metadata. +type WorkloadCredential struct { + Metadata Metadata + CertificatePem string + PrivateKeyPem string +} + +/* +metadata key instance/workload-trusted-root-certs + + { + "status": "OK", + "rootCertificates": { + "PROJECT.svc.id.goog": { + "metadata": { + "workload_creds_dir_path": "/var/run/secrets/workload-spiffe-credentials" + }, + "rootCertificatesPem": "-----BEGIN CERTIFICATE-----datahere-----END CERTIFICATE-----" + } + } + } +*/ + +// WorkloadTrustedRootCerts represents Workload Trusted Root Certs in metadata. +type WorkloadTrustedRootCerts struct { + Status string + RootCertificates map[string]RootCertificate +} + +// UnmarshalJSON is a custom JSON unmarshaller for WorkloadTrustedRootCerts +func (wtrc *WorkloadTrustedRootCerts) UnmarshalJSON(b []byte) error { + tmp := map[string]json.RawMessage{} + err := json.Unmarshal(b, &tmp) + if err != nil { + return err + } + + if err := json.Unmarshal(tmp["status"], &wtrc.Status); err != nil { + return err + } + + wtrc.RootCertificates = map[string]RootCertificate{} + rcs := map[string]json.RawMessage{} + if err := json.Unmarshal(tmp["rootCertificates"], &rcs); err != nil { + return err + } + + for domain, value := range rcs { + rc := RootCertificate{} + err := json.Unmarshal(value, &rc) + if err != nil { + return err + } + wtrc.RootCertificates[domain] = rc + } + + return nil +} + +// RootCertificate represents a Root Certificate in metadata +type RootCertificate struct { + Metadata Metadata + RootCertificatesPem string +} + +// Metadata represents Metadata in metadata +type Metadata struct { + WorkloadCredsDirPath string +} + +func main() { + ctx := context.Background() + + opts := logger.LogOpts{ + LoggerName: programName, + FormatFunction: logFormat, + } + + opts.Writers = []io.Writer{os.Stderr} + logger.Init(ctx, opts) + defer logger.Infof("Done") + + // TODO: prune old dirs + + if err := refreshCreds(); err != nil { + logger.Fatalf(err.Error()) + } + +} + +func refreshCreds() error { + project, err := getMetadata("project/project-id") + if err != nil { + return fmt.Errorf("Error getting project ID: %v", err) + } + domain := fmt.Sprintf("%s.svc.id.goog", project) + logger.Infof("Rotating workload credentials for domain %s", domain) + + wisMd, err := getMetadata("instance/workload-identities") + if err != nil { + return fmt.Errorf("Error getting workload-identities: %v", err) + } + + wtrcsMd, err := getMetadata("instance/workload-trusted-root-certs") + if err != nil { + return fmt.Errorf("Error getting workload-identities: %v", err) + } + + wis := WorkloadIdentities{} + if err := json.Unmarshal(wisMd, &wis); err != nil { + return fmt.Errorf("Error unmarshaling workload trusted root certs: %v", err) + } + + wtrcs := WorkloadTrustedRootCerts{} + if err := json.Unmarshal(wtrcsMd, &wtrcs); err != nil { + return fmt.Errorf("Error unmarshaling workload trusted root certs: %v", err) + } + + 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) + + // TODO: validate filesystem permissions + if err := os.MkdirAll(contentDir, 0750); err != nil { + return fmt.Errorf("Error creating contents dir: %v", err) + } + + if err := os.WriteFile(fmt.Sprintf("%s/certificates.pem", contentDir), []byte(wis.WorkloadCredentials[domain].CertificatePem), 0666); 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), 0666); err != nil { + return fmt.Errorf("Error writing private_key.pem: %v", err) + } + + if err := os.WriteFile(fmt.Sprintf("%s/ca_certificates.pem", contentDir), []byte(wtrcs.RootCertificates[domain].RootCertificatesPem), 0666); err != nil { + return fmt.Errorf("Error writing ca_certificates.pem: %v", err) + } + + if err := os.Symlink(contentDir, tempSymlink); err != nil { + return fmt.Errorf("Error creating temporary link: %v", err) + } + + logger.Infof("Rotating symlink %s", symlink) + + if err := os.Rename(tempSymlink, symlink); err != nil { + return fmt.Errorf("Error rotating target link: %v", err) + } + + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20220713.00/google_guest_agent/non_windows_accounts.go new/guest-agent-20220824.00/google_guest_agent/non_windows_accounts.go --- old/guest-agent-20220713.00/google_guest_agent/non_windows_accounts.go 2022-06-22 23:00:27.000000000 +0200 +++ new/guest-agent-20220824.00/google_guest_agent/non_windows_accounts.go 2022-08-24 03:13:05.000000000 +0200 @@ -56,6 +56,16 @@ return true } +func removeExpiredKeys(keys []string) []string { + var validKeys []string + for _, key := range keys { + if err := utils.CheckExpiredKey(key); err == nil { + validKeys = append(validKeys, key) + } + } + return validKeys +} + type accountsMgr struct{} func (a *accountsMgr) diff() bool { @@ -72,7 +82,7 @@ // If any on-disk keys have expired. for _, keys := range sshKeys { - if len(keys) != len(getUserKeys(keys)) { + if len(keys) != len(removeExpiredKeys(keys)) { return true } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20220713.00/google_guest_agent/windows_accounts_test.go new/guest-agent-20220824.00/google_guest_agent/windows_accounts_test.go --- old/guest-agent-20220713.00/google_guest_agent/windows_accounts_test.go 2022-06-22 23:00:27.000000000 +0200 +++ new/guest-agent-20220824.00/google_guest_agent/windows_accounts_test.go 2022-08-24 03:13:05.000000000 +0200 @@ -264,6 +264,46 @@ } } +func TestRemoveExpiredKeys(t *testing.T) { + var tests = []struct { + key string + valid bool + }{ + {`user:ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2028-11-08T19:30:47+0000"}`, true}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2028-11-08T19:30:47+0700"}`, true}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2028-11-08T19:30:47+0700", "futureField": "UNUSED_FIELDS_IGNORED"}`, true}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2018-11-08T19:30:46+0000"}`, false}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2018-11-08T19:30:46+0700"}`, false}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"INVALID_TIMESTAMP"}`, false}, + {`user:ssh-rsa [KEY] google-ssh`, false}, + {`user:ssh-rsa [KEY] user`, true}, + {`user:ssh-rsa [KEY]`, true}, + // having the user: prefix should not affect whether a key is expired, repeat test cases without user: prefix + {`ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2028-11-08T19:30:47+0000"}`, true}, + {`ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2028-11-08T19:30:47+0700"}`, true}, + {`ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2028-11-08T19:30:47+0700", "futureField": "UNUSED_FIELDS_IGNORED"}`, true}, + {`ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2018-11-08T19:30:46+0000"}`, false}, + {`ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"2018-11-08T19:30:46+0700"}`, false}, + {`ssh-rsa [KEY] google-ssh {"userName":"u...@email.com", "expireOn":"INVALID_TIMESTAMP"}`, false}, + {`ssh-rsa [KEY] google-ssh`, false}, + {`ssh-rsa [KEY] user`, true}, + {`ssh-rsa [KEY]`, true}, + {}, + } + + for _, tt := range tests { + ret := removeExpiredKeys([]string{tt.key}) + if tt.valid { + if len(ret) == 0 || ret[0] != tt.key { + t.Errorf("valid key was removed: %q", tt.key) + } + } + if !tt.valid && len(ret) == 1 { + t.Errorf("invalid key was kept: %q", tt.key) + } + } +} + func TestVersionOk(t *testing.T) { tests := []struct { version versionInfo diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20220713.00/utils/main.go new/guest-agent-20220824.00/utils/main.go --- old/guest-agent-20220713.00/utils/main.go 2022-06-22 23:00:27.000000000 +0200 +++ new/guest-agent-20220824.00/utils/main.go 2022-08-24 03:13:05.000000000 +0200 @@ -25,7 +25,7 @@ "github.com/tarm/serial" ) -//ContainsString checks for the presence of a string in a slice. +// ContainsString checks for the presence of a string in a slice. func ContainsString(s string, ss []string) bool { for _, a := range ss { if a == s { @@ -35,12 +35,46 @@ return false } -type sshKeyData struct { +type sshExpiration struct { ExpireOn string UserName string } -//CheckExpired takes a time string and determines if it represents a time in the past. +// CheckExpiredKey validates whether a key has expired. Keys with invalid expiration formats will result in an +// error. +func CheckExpiredKey(key string) error { + trimmedKey := strings.Trim(key, " ") + if trimmedKey == "" { + return errors.New("Invalid ssh key entry - empty key") + } + fields := strings.SplitN(trimmedKey, " ", 4) + if len(fields) < 3 { + // Non-expiring key. + return nil + } + if len(fields) == 3 && fields[2] == "google-ssh" { + // expiring key without expiration format. + return errors.New("Invalid ssh key entry - expiration missing") + } + if len(fields) > 3 { + lkey := sshExpiration{} + if err := json.Unmarshal([]byte(fields[3]), &lkey); err != nil { + // invalid expiration format. + return err + } + expired, err := CheckExpired(lkey.ExpireOn) + if err != nil { + return err + } + if expired { + return errors.New("Invalid ssh key entry - expired key") + } + } + + return nil +} + +// CheckExpired takes a time string and determines if it represents a time in the past. func CheckExpired(expireOn string) (bool, error) { t, err := time.Parse(time.RFC3339, expireOn) if err != nil { @@ -54,10 +88,9 @@ } -//GetUserKey takes a string and determines if it is a valid SSH key and returns -//the user and key if valid, nil otherwise. +// GetUserKey takes a string and determines if it is a valid SSH key and returns +// the user and key if valid, nil otherwise. func GetUserKey(rawKey string) (string, string, error) { - key := strings.Trim(rawKey, " ") if key == "" { return "", "", errors.New("Invalid ssh key entry - empty key") @@ -70,30 +103,14 @@ if user == "" { return "", "", errors.New("Invalid ssh key entry - user missing") } - fields := strings.SplitN(key, " ", 4) - if len(fields) == 3 && fields[2] == "google-ssh" { - // expiring key without expiration format. - return "", "", errors.New("Invalid ssh key entry - expiration missing") - } - if len(fields) > 3 { - lkey := sshKeyData{} - if err := json.Unmarshal([]byte(fields[3]), &lkey); err != nil { - // invalid expiration format. - return "", "", err - } - expired, err := CheckExpired(lkey.ExpireOn) - if err != nil { - return "", "", err - } - if expired { - return "", "", errors.New("Invalid ssh key entry - expired key") - } + if err := CheckExpiredKey(key[idx+1:]); err != nil { + return "", "", err } return user, key[idx+1:], nil } -//SerialPort is a type for writing to a named serial port. +// SerialPort is a type for writing to a named serial port. type SerialPort struct { Port string } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20220713.00/utils/main_test.go new/guest-agent-20220824.00/utils/main_test.go --- old/guest-agent-20220713.00/utils/main_test.go 2022-06-22 23:00:27.000000000 +0200 +++ new/guest-agent-20220824.00/utils/main_test.go 2022-08-24 03:13:05.000000000 +0200 @@ -59,3 +59,27 @@ } } } + +func TestCheckExpiredKey(t *testing.T) { + table := []struct { + key string + expired bool + }{ + {`usera:ssh-rsa AAAA1234 google-ssh {"userName":"us...@example.com","expireOn":"2095-04-23T12:34:56+0000"}`, false}, + {`usera:ssh-rsa AAAA1234 google-ssh {"userName":"us...@example.com","expireOn":"2021-04-23T12:34:56+0000"}`, true}, + {`usera:ssh-rsa AAAA1234 google-ssh {"userName":"us...@example.com","expireOn":"Apri 4, 2056"}`, true}, + {`usera:ssh-rsa AAAA1234 google-ssh`, true}, + {" ", true}, + {"ssh-rsa AAAA1234", false}, + {":ssh-rsa AAAA1234", false}, + {"usera:ssh-rsa AAAA1234", false}, + } + + for _, tt := range table { + err := CheckExpiredKey(tt.key) + isExpired := err != nil + if isExpired != tt.expired { + t.Errorf("CheckExpiredKey(%s) incorrect return: expired: %t - want expired: %t", tt.key, isExpired, tt.expired) + } + } +} ++++++ vendor.tar.gz ++++++