cmd/core_plugin/winpassreset/winpassreset_windows.go (151 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows // Package winpassreset is responsible for managing windows password resets. package winpassreset import ( "context" "encoding/json" "errors" "fmt" "github.com/GoogleCloudPlatform/galog" "github.com/GoogleCloudPlatform/google-guest-agent/cmd/core_plugin/manager" "github.com/GoogleCloudPlatform/google-guest-agent/internal/accounts" "github.com/GoogleCloudPlatform/google-guest-agent/internal/events" "github.com/GoogleCloudPlatform/google-guest-agent/internal/lru" "github.com/GoogleCloudPlatform/google-guest-agent/internal/metadata" "github.com/GoogleCloudPlatform/google-guest-agent/internal/reg" "golang.org/x/sys/windows/registry" ) const ( // accountsRegKey is the registry key where the user accounts are stored. accountsRegKey = "PublicKeys" // winpassModuleID is the name of the module. winpassModuleID = "winpassreset" ) var ( // badReg is a list of bad registry keys that we don't want to log. badReg = lru.New[string](64) // The following are stubbed out for error injection in tests. regWriteMultiString = reg.WriteMultiString regReadMultiString = reg.ReadMultiString resetPassword = defaultResetPassword modifiedKeys = defaultModifiedKeys ) // NewModule returns the windows password reset module. func NewModule(_ context.Context) *manager.Module { return &manager.Module{ ID: winpassModuleID, Setup: moduleSetup, Description: "Resets the password for a user on a Windows VM", } } // moduleSetup initializes the module. func moduleSetup(ctx context.Context, data any) error { desc, ok := data.(*metadata.Descriptor) if !ok { return fmt.Errorf("winpass module expects a metadata descriptor in the data pointer") } if err := setupAccounts(ctx, desc.Instance().Attributes().WindowsKeys()); err != nil { galog.Errorf("failed to reset password: %v", err) } eManager := events.FetchManager() sub := events.EventSubscriber{Name: winpassModuleID, Callback: eventCallback} eManager.Subscribe(metadata.LongpollEvent, sub) return nil } // eventCallback is the callback event handler for the winpass module. func eventCallback(ctx context.Context, evType string, data any, evData *events.EventData) bool { desc, ok := evData.Data.(*metadata.Descriptor) // If the event manager is passing a non expected data type we log it and // don't renew the handler. if !ok { galog.Errorf("event's data is not a metadata descriptor: %+v", evData.Data) return false } // If the event manager is passing/reporting an error we log it and keep // renewing the handler. if evData.Error != nil { galog.Debugf("metadata event watcher reported error: %s, skipping.", evData.Error) return true } if err := setupAccounts(ctx, desc.Instance().Attributes().WindowsKeys()); err != nil { galog.Errorf("failed to reset password: %v", err) } return true } // setupAccounts sets up accounts in the registry and creates and updates them // as needed. func setupAccounts(ctx context.Context, newKeys []*metadata.WindowsKey) error { regKeys, err := regReadMultiString(reg.GCEKeyBase, accountsRegKey) if err != nil && !errors.Is(err, registry.ErrNotExist) { return fmt.Errorf("failed to read registry keys: %w", err) } diffKeys := modifiedKeys(regKeys, newKeys) // Create or update the accounts in the machine. for _, key := range diffKeys { if err := resetPassword(ctx, key); err != nil { galog.Errorf("error setting password for user %s: %v", key.UserName(), err) } } // Update the registry with the new keys. var jsonKeys []string for _, key := range newKeys { jsonKey, err := json.Marshal(key) if err != nil { return fmt.Errorf("failed to marshal key: %w", err) } jsonKeys = append(jsonKeys, string(jsonKey)) } return regWriteMultiString(reg.GCEKeyBase, accountsRegKey, jsonKeys) } // defaultModifiedKeys determines which keys are new or modified. This does not // handle keys that are in the registry but not in the metadata. func defaultModifiedKeys(regKeys []string, newKeys []*metadata.WindowsKey) []*metadata.WindowsKey { if len(newKeys) == 0 { return nil } if len(regKeys) == 0 { return newKeys } // Convert the registry keys to WindowsKey. oldKeys := regKeysToWindowsKey(regKeys) var toAdd []*metadata.WindowsKey for _, key := range newKeys { isDiff := true for _, oldKey := range oldKeys { // If the user name, modulus and expiry are the same, the key is not // different. if oldKey.UserName() == key.UserName() && oldKey.Modulus() == key.Modulus() && oldKey.ExpireOn() == key.ExpireOn() { isDiff = false break } } if isDiff { toAdd = append(toAdd, key) } } return toAdd } // regKeysToWindowsKey converts a list of registry keys to a list of WindowsKey. // Ignores bad registry keys. func regKeysToWindowsKey(regKeys []string) []*metadata.WindowsKey { var winKeys []*metadata.WindowsKey for _, s := range regKeys { key := &metadata.WindowsKey{} if err := key.UnmarshalJSON([]byte(s)); err != nil { if _, found := badReg.Get(s); !found { galog.Errorf("bad windows key from registry: %s", err) badReg.Put(s, true) } continue } winKeys = append(winKeys, key) } return winKeys } // defaultResetPassword resets the password of the user specified in the key. // If the user does not exist, it will create it. func defaultResetPassword(ctx context.Context, key *metadata.WindowsKey) error { newPassword, err := accounts.GeneratePassword(key.PasswordLength()) if err != nil { return fmt.Errorf("failed to generate password: %w", err) } u, err := accounts.FindUser(ctx, key.UserName()) if err != nil { // If the user does not exist, we create it. newUser := &accounts.User{ Name: key.UserName(), Password: newPassword, } err = accounts.CreateUser(ctx, newUser) if err != nil { return fmt.Errorf("failed to create user: %w", err) } u, err = accounts.FindUser(ctx, newUser.Name) if err != nil { return fmt.Errorf("failed to find user: %w", err) } } else { // If the user exists, we simply update the password. if err = u.SetPassword(ctx, newPassword); err != nil { return fmt.Errorf("failed to set password: %w", err) } } // Add the user to the administrator group if needed. if key.AddToAdministrator() != nil && *key.AddToAdministrator() { if err = accounts.AddUserToGroup(ctx, u, accounts.AdminGroup); err != nil { return fmt.Errorf("failed to add user to administrator group: %w", err) } } return nil }