commit google-guest-agent for openSUSE:Factory
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 2021-12-01 20:47:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/google-guest-agent (Old) and /work/SRC/openSUSE:Factory/.google-guest-agent.new.31177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "google-guest-agent" Wed Dec 1 20:47:16 2021 rev:9 rq:934874 version:20211116.00 Changes: -------- --- /work/SRC/openSUSE:Factory/google-guest-agent/google-guest-agent.changes 2021-10-23 00:52:10.785153225 +0200 +++ /work/SRC/openSUSE:Factory/.google-guest-agent.new.31177/google-guest-agent.changes 2021-12-02 02:13:50.658920685 +0100 @@ -1,0 +2,17 @@ +Thu Nov 18 13:33:12 UTC 2021 - John Paul Adrian Glaubitz <adrian.glaubitz@suse.com> + +- Update to version 20211116.00 (bsc#1193257, bsc#1193258) + * dont duplicate logs (#146) + * Add WantedBy network dependencies to google-guest-agent service (#136) + * dont try dhcpv6 when not needed (#145) + * Integration tests: instance setup (#143) + * Integration test: test create and remove google user (#128) + * handle comm errors in script runner (#140) + * enforce script ordering (#138) + * enable ipv6 on secondary interfaces (#133) +- from version 20211103.00 + * Integration tests: instance setup (#143) +- from version 20211027.00 + * Integration test: test create and remove google user (#128) + +------------------------------------------------------------------- Old: ---- guest-agent-20211019.00.tar.gz New: ---- guest-agent-20211116.00.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ google-guest-agent.spec ++++++ --- /var/tmp/diff_new_pack.vaPcnv/_old 2021-12-02 02:13:51.270918818 +0100 +++ /var/tmp/diff_new_pack.vaPcnv/_new 2021-12-02 02:13:51.274918807 +0100 @@ -24,7 +24,7 @@ %global import_path %{provider_prefix} Name: google-guest-agent -Version: 20211019.00 +Version: 20211116.00 Release: 0 Summary: Google Cloud Guest Agent License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.vaPcnv/_old 2021-12-02 02:13:51.306918709 +0100 +++ /var/tmp/diff_new_pack.vaPcnv/_new 2021-12-02 02:13:51.306918709 +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">20211019.00</param> - <param name="revision">20211019.00</param> + <param name="versionformat">20211116.00</param> + <param name="revision">20211116.00</param> <param name="changesgenerate">enable</param> </service> <service name="recompress" mode="disabled"> @@ -15,6 +15,6 @@ <param name="basename">guest-agent</param> </service> <service name="go_modules" mode="disabled"> - <param name="archive">guest-agent-20211019.00.tar.gz</param> + <param name="archive">guest-agent-20211116.00.tar.gz</param> </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.vaPcnv/_old 2021-12-02 02:13:51.322918660 +0100 +++ /var/tmp/diff_new_pack.vaPcnv/_new 2021-12-02 02:13:51.322918660 +0100 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/GoogleCloudPlatform/guest-agent/</param> - <param name="changesrevision">a1c10d174f1e9cb026585c6b141e1d4c8349e1ba</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">b0c8cbdfb9e74a4ef05e0ac09faf20e83eddbbcc</param></service></servicedata> \ No newline at end of file ++++++ guest-agent-20211019.00.tar.gz -> guest-agent-20211116.00.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google-guest-agent.service new/guest-agent-20211116.00/google-guest-agent.service --- old/guest-agent-20211019.00/google-guest-agent.service 2021-10-20 00:09:13.000000000 +0200 +++ new/guest-agent-20211116.00/google-guest-agent.service 2021-11-11 20:48:43.000000000 +0100 @@ -17,8 +17,8 @@ ExecStart=/usr/bin/google_guest_agent OOMScoreAdjust=-999 Restart=always -StandardOutput=journal+console [Install] WantedBy=sshd.service WantedBy=multi-user.target +WantedBy=network.service networking.service NetworkManager.service systemd-networkd.service diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google-shutdown-scripts.service new/guest-agent-20211116.00/google-shutdown-scripts.service --- old/guest-agent-20211019.00/google-shutdown-scripts.service 2021-10-20 00:09:13.000000000 +0200 +++ new/guest-agent-20211116.00/google-shutdown-scripts.service 2021-11-11 20:48:43.000000000 +0100 @@ -11,7 +11,6 @@ ExecStop=/usr/bin/google_metadata_script_runner shutdown TimeoutStopSec=0 KillMode=process -StandardOutput=journal+console [Install] WantedBy=multi-user.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google-startup-scripts.service new/guest-agent-20211116.00/google-startup-scripts.service --- old/guest-agent-20211019.00/google-startup-scripts.service 2021-10-20 00:09:13.000000000 +0200 +++ new/guest-agent-20211116.00/google-startup-scripts.service 2021-11-11 20:48:43.000000000 +0100 @@ -9,7 +9,6 @@ ExecStart=/usr/bin/google_metadata_script_runner startup #TimeoutStartSec is ignored for Type=oneshot service units. KillMode=process -StandardOutput=journal+console [Install] WantedBy=multi-user.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google_guest_agent/accounts_test.go new/guest-agent-20211116.00/google_guest_agent/accounts_test.go --- old/guest-agent-20211019.00/google_guest_agent/accounts_test.go 2021-10-20 00:09:13.000000000 +0200 +++ new/guest-agent-20211116.00/google_guest_agent/accounts_test.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,247 +0,0 @@ -// Copyright 2017 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" - "encoding/base64" - "hash" - "math/big" - "reflect" - "testing" - "time" - "unicode" - - "github.com/go-ini/ini" -) - -func mkptr(b bool) *bool { - ret := b - return &ret -} - -func TestExpired(t *testing.T) { - var tests = []struct { - sTime string - e bool - }{ - {time.Now().Add(5 * time.Minute).Format(time.RFC3339), false}, - {time.Now().Add(-5 * time.Minute).Format(time.RFC3339), true}, - {"some bad time", true}, - } - - for _, tt := range tests { - k := windowsKey{ExpireOn: tt.sTime} - if tt.e != k.expired() { - t.Errorf("windowsKey.expired() with ExpiredOn %q should return %t", k.ExpireOn, tt.e) - } - } -} - -func TestAccountsDisabled(t *testing.T) { - var tests = []struct { - name string - data []byte - md *metadata - want bool - }{ - {"not explicitly disabled", []byte(""), &metadata{}, false}, - {"enabled in cfg only", []byte("[accountManager]\ndisable=false"), &metadata{}, false}, - {"disabled in cfg only", []byte("[accountManager]\ndisable=true"), &metadata{}, true}, - {"disabled in cfg, enabled in instance metadata", []byte("[accountManager]\ndisable=true"), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(false)}}}, true}, - {"enabled in cfg, disabled in instance metadata", []byte("[accountManager]\ndisable=false"), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, false}, - {"enabled in instance metadata only", []byte(""), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(false)}}}, false}, - {"enabled in project metadata only", []byte(""), &metadata{Project: project{Attributes: attributes{DisableAccountManager: mkptr(false)}}}, false}, - {"disabled in instance metadata only", []byte(""), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, true}, - {"enabled in instance metadata, disabled in project metadata", []byte(""), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(false)}}, Project: project{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, false}, - {"disabled in project metadata only", []byte(""), &metadata{Project: project{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, true}, - } - - for _, tt := range tests { - cfg, err := ini.InsensitiveLoad(tt.data) - if err != nil { - t.Errorf("test case %q: error parsing config: %v", tt.name, err) - continue - } - if cfg == nil { - cfg = &ini.File{} - } - newMetadata = tt.md - config = cfg - got := (&winAccountsMgr{}).disabled("windows") - if got != tt.want { - t.Errorf("test case %q, accounts.disabled() got: %t, want: %t", tt.name, got, tt.want) - } - } - got := (&winAccountsMgr{}).disabled("linux") - if got != true { - t.Errorf("winAccountsMgr.disabled(\"linux\") got: %t, want: true", got) - } -} - -// rename this with leading disabled because this is a resource -// intensive test. this test takes approx. 141 seconds to complete, next -// longest test is 0.43 seconds. -func disabledTestNewPwd(t *testing.T) { - minPasswordLength := 15 - maxPasswordLength := 255 - var tests = []struct { - name string - passwordLength int - wantPasswordLength int - }{ - {"0 characters, default value", 0, minPasswordLength}, - {"5 characters, below min", 5, minPasswordLength}, - {"15 characters", 5, minPasswordLength}, - {"30 characters", 30, 30}, - {"127 characters", 127, 127}, - {"254 characters", 254, 254}, - {"256 characters", 256, maxPasswordLength}, - } - - for _, tt := range tests { - for i := 0; i < 100000; i++ { - pwd, err := newPwd(tt.passwordLength) - if err != nil { - t.Fatal(err) - } - if len(pwd) != tt.wantPasswordLength { - t.Errorf("Password is not %d characters: len(%s)=%d", tt.wantPasswordLength, pwd, len(pwd)) - } - var l, u, n, s int - for _, r := range pwd { - switch { - case unicode.IsLower(r): - l = 1 - case unicode.IsUpper(r): - u = 1 - case unicode.IsDigit(r): - n = 1 - case unicode.IsPunct(r) || unicode.IsSymbol(r): - s = 1 - } - } - if l+u+n+s < 3 { - t.Errorf("Password does not have at least one character from 3 categories: '%v'", pwd) - } - } - } -} - -func TestCreatecredsJSON(t *testing.T) { - pwd := "password" - prv, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatalf("error generating key: %v", err) - } - k := windowsKey{ - Email: "email", - ExpireOn: "expire", - Exponent: base64.StdEncoding.EncodeToString(new(big.Int).SetInt64(int64(prv.PublicKey.E)).Bytes()), - Modulus: base64.StdEncoding.EncodeToString(prv.PublicKey.N.Bytes()), - UserName: "username", - } - for name, hashFunc := range map[string]hash.Hash{"": sha1.New(), "sha1": sha1.New(), "sha256": sha256.New(), "sha512": sha512.New()} { - k.HashFunction = name - c, err := createcredsJSON(k, pwd) - if err != nil { - t.Fatalf("error running createcredsJSON: %v", err) - } - if k.HashFunction == "" { - k.HashFunction = "sha1" - } - - bPwd, err := base64.StdEncoding.DecodeString(c.EncryptedPassword) - if err != nil { - t.Fatalf("error base64 decoding encoded pwd: %v", err) - } - decPwd, err := rsa.DecryptOAEP(hashFunc, rand.Reader, prv, bPwd, nil) - if err != nil { - t.Fatalf("error decrypting password: %v", err) - } - if pwd != string(decPwd) { - t.Errorf("decrypted password does not match expected for hash func %q, got: %s, want: %s", name, string(decPwd), pwd) - } - if k.UserName != c.UserName { - t.Errorf("returned credsJSON UserName field unexpected, got: %s, want: %s", c.UserName, k.UserName) - } - if k.HashFunction != c.HashFunction { - t.Errorf("returned credsJSON HashFunction field unexpected, got: %s, want: %s", c.HashFunction, k.HashFunction) - } - if !c.PasswordFound { - t.Error("returned credsJSON PasswordFound field is not true") - } - } -} - -func TestCompareAccounts(t *testing.T) { - var tests = []struct { - newKeys windowsKeys - oldStrKeys []string - wantAdd windowsKeys - }{ - // These should return toAdd: - // In MD, not Reg - {windowsKeys{{UserName: "foo"}}, nil, windowsKeys{{UserName: "foo"}}}, - {windowsKeys{{UserName: "foo"}}, []string{`{"UserName":"bar"}`}, windowsKeys{{UserName: "foo"}}}, - - // These should return nothing: - // In Reg and MD - {windowsKeys{{UserName: "foo"}}, []string{`{"UserName":"foo"}`}, nil}, - // In Reg, not MD - {nil, []string{`{UserName":"foo"}`}, nil}, - } - - for _, tt := range tests { - toAdd := compareAccounts(tt.newKeys, tt.oldStrKeys) - if !reflect.DeepEqual(tt.wantAdd, toAdd) { - t.Errorf("toAdd does not match expected: newKeys: %v, oldStrKeys: %q, got: %v, want: %v", tt.newKeys, tt.oldStrKeys, toAdd, tt.wantAdd) - } - } -} - -func TestRemoveExpiredKeys(t *testing.T) { - var tests = []struct { - key string - valid bool - }{ - {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0000"}`, true}, - {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700"}`, true}, - {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700", "futureField": "UNUSED_FIELDS_IGNORED"}`, true}, - {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0000"}`, false}, - {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0700"}`, false}, - {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"INVALID_TIMESTAMP"}`, false}, - {`user:ssh-rsa [KEY] google-ssh`, false}, - {`user:ssh-rsa [KEY] user`, true}, - {`user:ssh-rsa [KEY]`, true}, - {}, - } - - 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) - } - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google_guest_agent/addresses.go new/guest-agent-20211116.00/google_guest_agent/addresses.go --- old/guest-agent-20211019.00/google_guest_agent/addresses.go 2021-10-20 00:09:13.000000000 +0200 +++ new/guest-agent-20211116.00/google_guest_agent/addresses.go 2021-11-11 20:48:43.000000000 +0100 @@ -431,7 +431,7 @@ func configureIPv6() error { var newNi, oldNi networkInterfaces if len(newMetadata.Instance.NetworkInterfaces) == 0 { - return fmt.Errorf("No interfaces found in metadata") + return fmt.Errorf("no interfaces found in metadata") } newNi = newMetadata.Instance.NetworkInterfaces[0] if len(oldMetadata.Instance.NetworkInterfaces) > 0 { @@ -543,6 +543,10 @@ return err } + if len(googleIpv6Interfaces) == 0 { + return nil + } + var dhclientArgs6 []string dhclientArgs6 = append([]string{"-6"}, googleIpv6Interfaces...) return runCmd(exec.Command("dhclient", dhclientArgs6...)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google_guest_agent/instance_setup_integ_test.go new/guest-agent-20211116.00/google_guest_agent/instance_setup_integ_test.go --- old/guest-agent-20211019.00/google_guest_agent/instance_setup_integ_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/guest-agent-20211116.00/google_guest_agent/instance_setup_integ_test.go 2021-11-11 20:48:43.000000000 +0100 @@ -0,0 +1,235 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build integration + +package main + +import ( + "context" + "io/ioutil" + "os" + "strings" + "testing" +) + +const ( + botoCfg = "/etc/boto.cfg" +) + +// TestInstanceSetupSSHKeys validates SSH keys are generated on first boot and not changed afterward. +func TestInstanceSetupSSHKeys(t *testing.T) { + cfg, err := parseConfig("") // get empty config + if err != nil { + t.Fatal("failed to init config object") + } + config = cfg // set the global + defer func() { config = nil }() // unset at end of test + + tempdir, err := ioutil.TempDir("/tmp", "test_instance_setup") + if err != nil { + t.Fatal("failed to create working dir") + } + + // Configure a non-standard instance ID dir for us to play with. + config.Section("Instance").Key("instance_id_dir").SetValue(tempdir) + config.Section("InstanceSetup").Key("host_key_dir").SetValue(tempdir) + + ctx := context.Background() + agentInit(ctx) + + if _, err := os.Stat(tempdir + "/google_instance_id"); err != nil { + t.Fatal("instance ID File was not created by agentInit") + } + + dir, err := os.Open(tempdir) + if err != nil { + t.Fatal("failed to open working dir") + } + defer dir.Close() + + files, err := dir.Readdirnames(0) + if err != nil { + t.Fatal("failed to read files") + } + + var keys []string + for _, file := range files { + if strings.HasPrefix(file, "ssh_host_") { + keys = append(keys, file) + } + } + + if len(keys) == 0 { + t.Fatal("instance setup didn't create SSH host keys") + } + + // Remove one key file and run again to confirm SSH keys have not + // changed because the instance ID file has not changed. + if err := os.Remove(tempdir + "/" + keys[0]); err != nil { + t.Fatal("failed to remove key file") + } + + agentInit(ctx) + + if _, err := dir.Seek(0, 0); err != nil { + t.Fatal("failed to rewind dir for second check") + } + files2, err := dir.Readdirnames(0) + if err != nil { + t.Fatal("failed to read files") + } + + var keys2 []string + for _, file := range files2 { + if strings.HasPrefix(file, "ssh_host_") { + keys2 = append(keys2, file) + } + if file == keys[0] { + t.Fatalf("agentInit recreated key %s", file) + } + } + + if len(keys) == len(keys2) { + t.Fatal("agentInit recreated SSH host keys") + } +} + +// TestInstanceSetupSSHKeysDisabled validates the config option to disable host +// key generation is respected. +func TestInstanceSetupSSHKeysDisabled(t *testing.T) { + cfg, err := parseConfig("") // get empty config + if err != nil { + t.Fatal("failed to init config object") + } + config = cfg // set the global + defer func() { config = nil }() // unset at end of test + + tempdir, err := ioutil.TempDir("/tmp", "test_instance_setup") + if err != nil { + t.Fatal("failed to create working dir") + } + + // Configure a non-standard instance ID dir for us to play with. + config.Section("Instance").Key("instance_id_dir").SetValue(tempdir) + config.Section("InstanceSetup").Key("host_key_dir").SetValue(tempdir) + + // Disable SSH host key generation. + config.Section("InstanceSetup").Key("set_host_keys").SetValue("false") + + ctx := context.Background() + agentInit(ctx) + + dir, err := os.Open(tempdir) + if err != nil { + t.Fatal("failed to open working dir") + } + defer dir.Close() + + files, err := dir.Readdirnames(0) + if err != nil { + t.Fatal("failed to read files") + } + + for _, file := range files { + if strings.HasPrefix(file, "ssh_host_") { + t.Fatal("agentInit created SSH host keys when disabled") + } + } +} + +func TestInstanceSetupBotoConfig(t *testing.T) { + cfg, err := parseConfig("") // get empty config + if err != nil { + t.Fatal("failed to init config object") + } + config = cfg // set the global + defer func() { config = nil }() // unset at end of test + + tempdir, err := ioutil.TempDir("/tmp", "test_instance_setup") + if err != nil { + t.Fatal("failed to create working dir") + } + + // Configure a non-standard instance ID dir for us to play with. + config.Section("Instance").Key("instance_id_dir").SetValue(tempdir) + config.Section("InstanceSetup").Key("host_key_dir").SetValue(tempdir) + + ctx := context.Background() + + if err := os.Rename(botoCfg, botoCfg+".bak"); err != nil { + t.Fatalf("failed to move boto config: %v", err) + } + defer func() { + // Restore file at end of test. + if err := os.Rename(botoCfg+".bak", botoCfg); err != nil { + t.Fatalf("failed to restore boto config: %v", err) + } + }() + + // Test it is created by default on first boot + agentInit(ctx) + if _, err := os.Stat(botoCfg); err != nil { + t.Fatal("boto config was not created on first boot") + } + + // Test it is not recreated on subsequent invocations + if err := os.Remove(botoCfg); err != nil { + t.Fatal("failed to remove boto config") + } + agentInit(ctx) + if _, err := os.Stat(botoCfg); err == nil || !os.IsNotExist(err) { + // If we didn't get an error, or if we got some other kind of error + t.Fatal("boto config was recreated after first boot") + } +} + +func TestInstanceSetupBotoConfigDisabled(t *testing.T) { + cfg, err := parseConfig("") // get empty config + if err != nil { + t.Fatal("failed to init config object") + } + config = cfg // set the global + defer func() { config = nil }() // unset at end of test + + tempdir, err := ioutil.TempDir("/tmp", "test_instance_setup") + if err != nil { + t.Fatal("failed to create working dir") + } + + // Configure a non-standard instance ID dir for us to play with. + config.Section("Instance").Key("instance_id_dir").SetValue(tempdir) + config.Section("InstanceSetup").Key("host_key_dir").SetValue(tempdir) + + ctx := context.Background() + + if err := os.Rename(botoCfg, botoCfg+".bak"); err != nil { + t.Fatalf("failed to move boto config: %v", err) + } + defer func() { + // Restore file at end of test. + if err := os.Rename(botoCfg+".bak", botoCfg); err != nil { + t.Fatalf("failed to restore boto config: %v", err) + } + }() + + // Test it is not created if disabled in config. + config.Section("InstanceSetup").Key("set_boto_config").SetValue("false") + agentInit(ctx) + + if _, err := os.Stat(botoCfg); err == nil || !os.IsNotExist(err) { + // If we didn't get an error, or if we got some other kind of error + t.Fatal("boto config was created when disabled in config") + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google_guest_agent/non_windows_accounts.go new/guest-agent-20211116.00/google_guest_agent/non_windows_accounts.go --- old/guest-agent-20211019.00/google_guest_agent/non_windows_accounts.go 2021-10-20 00:09:13.000000000 +0200 +++ new/guest-agent-20211116.00/google_guest_agent/non_windows_accounts.go 2021-11-11 20:48:43.000000000 +0100 @@ -390,7 +390,6 @@ } gpasswddel := config.Section("Accounts").Key("gpasswd_remove_cmd").MustString("gpasswd -d {user} {group}") return runCmd(createUserGroupCmd(gpasswddel, user, "google-sudoers")) - } // createSudoersFile creates the google_sudoers configuration file if it does diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google_guest_agent/non_windows_accounts_integ_test.go new/guest-agent-20211116.00/google_guest_agent/non_windows_accounts_integ_test.go --- old/guest-agent-20211019.00/google_guest_agent/non_windows_accounts_integ_test.go 2021-10-20 00:09:13.000000000 +0200 +++ new/guest-agent-20211116.00/google_guest_agent/non_windows_accounts_integ_test.go 2021-11-11 20:48:43.000000000 +0100 @@ -3,10 +3,58 @@ package main import ( + "fmt" + "os" "os/exec" + "strings" "testing" ) +const ( + testUser = "integration-test-user" + defaultgroupstring = "adm,dip,docker,lxd,plugdev,video,google-sudoers" +) + +func TestCreateAndRemoveGoogleUser(t *testing.T) { + if exist, err := userExists(testUser); err != nil && exist { + t.Fatalf("test user should not exist") + } + if err := createGoogleUser(testUser); err != nil { + t.Errorf("createGoogleUser failed creating test user") + } + if exist, err := userExists(testUser); exist != true || err != nil { + t.Errorf("test user should exist") + } + cmd := exec.Command("groups", testUser) + ret := runCmdOutput(cmd) + if ret.ExitCode() != 0 { + t.Errorf("failed looking up groups for user: stdout:%s stderr:%s", ret.Stdout(), ret.Stderr()) + } + groups := strings.Split(strings.TrimSpace(strings.Split(ret.Stdout(), ":")[1]), " ") + expectedGroupString := config.Section("Accounts").Key("groups").MustString(defaultgroupstring) + expectedGroups := strings.Split(expectedGroupString, ",") + for _, group := range groups { + if !contains(group, expectedGroups) { + t.Errorf("test user has been added to an unexpected group %s", group) + } + } + if _, err := os.Stat(fmt.Sprintf("/home/%s", testUser)); err != nil { + t.Errorf("test user home directory does not exist") + } + if err := createGoogleUser(testUser); err == nil { + t.Errorf("createGoogleUser did not return error when creating user that already exists") + } + if err := removeGoogleUser(testUser); err != nil { + t.Errorf("removeGoogleUser did not remove user") + } + if exist, err := userExists(testUser); err != nil && exist == true { + t.Errorf("test user should not exist") + } + if err := removeGoogleUser(testUser); err == nil { + t.Errorf("removeGoogleUser did not return error when removing user that doesn't exist") + } +} + func TestGroupaddDuplicates(t *testing.T) { cmd := exec.Command("groupadd", "integ-test-group") ret := runCmdOutput(cmd) @@ -19,3 +67,12 @@ t.Fatalf("got wrong exit code running \"groupadd integ-test-group\", expected 9 got %v\n", ret.ExitCode()) } } + +func contains(target string, expected []string) bool { + for _, e := range expected { + if e == target { + return true + } + } + return false +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/guest-agent-20211019.00/google_guest_agent/windows_accounts_test.go new/guest-agent-20211116.00/google_guest_agent/windows_accounts_test.go --- old/guest-agent-20211019.00/google_guest_agent/windows_accounts_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/guest-agent-20211116.00/google_guest_agent/windows_accounts_test.go 2021-11-11 20:48:43.000000000 +0100 @@ -0,0 +1,247 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "hash" + "math/big" + "reflect" + "testing" + "time" + "unicode" + + "github.com/go-ini/ini" +) + +func mkptr(b bool) *bool { + ret := b + return &ret +} + +func TestExpired(t *testing.T) { + var tests = []struct { + sTime string + e bool + }{ + {time.Now().Add(5 * time.Minute).Format(time.RFC3339), false}, + {time.Now().Add(-5 * time.Minute).Format(time.RFC3339), true}, + {"some bad time", true}, + } + + for _, tt := range tests { + k := windowsKey{ExpireOn: tt.sTime} + if tt.e != k.expired() { + t.Errorf("windowsKey.expired() with ExpiredOn %q should return %t", k.ExpireOn, tt.e) + } + } +} + +func TestAccountsDisabled(t *testing.T) { + var tests = []struct { + name string + data []byte + md *metadata + want bool + }{ + {"not explicitly disabled", []byte(""), &metadata{}, false}, + {"enabled in cfg only", []byte("[accountManager]\ndisable=false"), &metadata{}, false}, + {"disabled in cfg only", []byte("[accountManager]\ndisable=true"), &metadata{}, true}, + {"disabled in cfg, enabled in instance metadata", []byte("[accountManager]\ndisable=true"), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(false)}}}, true}, + {"enabled in cfg, disabled in instance metadata", []byte("[accountManager]\ndisable=false"), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, false}, + {"enabled in instance metadata only", []byte(""), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(false)}}}, false}, + {"enabled in project metadata only", []byte(""), &metadata{Project: project{Attributes: attributes{DisableAccountManager: mkptr(false)}}}, false}, + {"disabled in instance metadata only", []byte(""), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, true}, + {"enabled in instance metadata, disabled in project metadata", []byte(""), &metadata{Instance: instance{Attributes: attributes{DisableAccountManager: mkptr(false)}}, Project: project{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, false}, + {"disabled in project metadata only", []byte(""), &metadata{Project: project{Attributes: attributes{DisableAccountManager: mkptr(true)}}}, true}, + } + + for _, tt := range tests { + cfg, err := ini.InsensitiveLoad(tt.data) + if err != nil { + t.Errorf("test case %q: error parsing config: %v", tt.name, err) + continue + } + if cfg == nil { + cfg = &ini.File{} + } + newMetadata = tt.md + config = cfg + got := (&winAccountsMgr{}).disabled("windows") + if got != tt.want { + t.Errorf("test case %q, accounts.disabled() got: %t, want: %t", tt.name, got, tt.want) + } + } + got := (&winAccountsMgr{}).disabled("linux") + if got != true { + t.Errorf("winAccountsMgr.disabled(\"linux\") got: %t, want: true", got) + } +} + +// rename this with leading disabled because this is a resource +// intensive test. this test takes approx. 141 seconds to complete, next +// longest test is 0.43 seconds. +func disabledTestNewPwd(t *testing.T) { + minPasswordLength := 15 + maxPasswordLength := 255 + var tests = []struct { + name string + passwordLength int + wantPasswordLength int + }{ + {"0 characters, default value", 0, minPasswordLength}, + {"5 characters, below min", 5, minPasswordLength}, + {"15 characters", 5, minPasswordLength}, + {"30 characters", 30, 30}, + {"127 characters", 127, 127}, + {"254 characters", 254, 254}, + {"256 characters", 256, maxPasswordLength}, + } + + for _, tt := range tests { + for i := 0; i < 100000; i++ { + pwd, err := newPwd(tt.passwordLength) + if err != nil { + t.Fatal(err) + } + if len(pwd) != tt.wantPasswordLength { + t.Errorf("Password is not %d characters: len(%s)=%d", tt.wantPasswordLength, pwd, len(pwd)) + } + var l, u, n, s int + for _, r := range pwd { + switch { + case unicode.IsLower(r): + l = 1 + case unicode.IsUpper(r): + u = 1 + case unicode.IsDigit(r): + n = 1 + case unicode.IsPunct(r) || unicode.IsSymbol(r): + s = 1 + } + } + if l+u+n+s < 3 { + t.Errorf("Password does not have at least one character from 3 categories: '%v'", pwd) + } + } + } +} + +func TestCreatecredsJSON(t *testing.T) { + pwd := "password" + prv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("error generating key: %v", err) + } + k := windowsKey{ + Email: "email", + ExpireOn: "expire", + Exponent: base64.StdEncoding.EncodeToString(new(big.Int).SetInt64(int64(prv.PublicKey.E)).Bytes()), + Modulus: base64.StdEncoding.EncodeToString(prv.PublicKey.N.Bytes()), + UserName: "username", + } + for name, hashFunc := range map[string]hash.Hash{"": sha1.New(), "sha1": sha1.New(), "sha256": sha256.New(), "sha512": sha512.New()} { + k.HashFunction = name + c, err := createcredsJSON(k, pwd) + if err != nil { + t.Fatalf("error running createcredsJSON: %v", err) + } + if k.HashFunction == "" { + k.HashFunction = "sha1" + } + + bPwd, err := base64.StdEncoding.DecodeString(c.EncryptedPassword) + if err != nil { + t.Fatalf("error base64 decoding encoded pwd: %v", err) + } + decPwd, err := rsa.DecryptOAEP(hashFunc, rand.Reader, prv, bPwd, nil) + if err != nil { + t.Fatalf("error decrypting password: %v", err) + } + if pwd != string(decPwd) { + t.Errorf("decrypted password does not match expected for hash func %q, got: %s, want: %s", name, string(decPwd), pwd) + } + if k.UserName != c.UserName { + t.Errorf("returned credsJSON UserName field unexpected, got: %s, want: %s", c.UserName, k.UserName) + } + if k.HashFunction != c.HashFunction { + t.Errorf("returned credsJSON HashFunction field unexpected, got: %s, want: %s", c.HashFunction, k.HashFunction) + } + if !c.PasswordFound { + t.Error("returned credsJSON PasswordFound field is not true") + } + } +} + +func TestCompareAccounts(t *testing.T) { + var tests = []struct { + newKeys windowsKeys + oldStrKeys []string + wantAdd windowsKeys + }{ + // These should return toAdd: + // In MD, not Reg + {windowsKeys{{UserName: "foo"}}, nil, windowsKeys{{UserName: "foo"}}}, + {windowsKeys{{UserName: "foo"}}, []string{`{"UserName":"bar"}`}, windowsKeys{{UserName: "foo"}}}, + + // These should return nothing: + // In Reg and MD + {windowsKeys{{UserName: "foo"}}, []string{`{"UserName":"foo"}`}, nil}, + // In Reg, not MD + {nil, []string{`{UserName":"foo"}`}, nil}, + } + + for _, tt := range tests { + toAdd := compareAccounts(tt.newKeys, tt.oldStrKeys) + if !reflect.DeepEqual(tt.wantAdd, toAdd) { + t.Errorf("toAdd does not match expected: newKeys: %v, oldStrKeys: %q, got: %v, want: %v", tt.newKeys, tt.oldStrKeys, toAdd, tt.wantAdd) + } + } +} + +func TestRemoveExpiredKeys(t *testing.T) { + var tests = []struct { + key string + valid bool + }{ + {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0000"}`, true}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700"}`, true}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2028-11-08T19:30:47+0700", "futureField": "UNUSED_FIELDS_IGNORED"}`, true}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0000"}`, false}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"2018-11-08T19:30:46+0700"}`, false}, + {`user:ssh-rsa [KEY] google-ssh {"userName":"user@email.com", "expireOn":"INVALID_TIMESTAMP"}`, false}, + {`user:ssh-rsa [KEY] google-ssh`, false}, + {`user:ssh-rsa [KEY] user`, true}, + {`user:ssh-rsa [KEY]`, true}, + {}, + } + + 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) + } + } +} ++++++ vendor.tar.gz ++++++
participants (1)
-
Source-Sync