internal/cfg/cfg.go (285 lines of code) (raw):

// Copyright 2023 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 cfg is package responsible to loading and accessing the guest // environment configuration. package cfg import ( "bytes" "fmt" "io" "sync" "text/template" "github.com/go-ini/ini" ) var ( // instance is the single instance of configuration sections, once loaded this // package should always return it. instance *Sections // 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 // configValues holds the defaults values for template. defaultConfigValues = map[string]string{ "baseStateDir": defaultBaseStateDir, "socketConnectionsDir": defaultSocketConnectionsDir, "instanceIDFile": defaultInstanceIDFile, "commandPipe": defaultCmdMonitor, "ipAliasesEnabled": fmt.Sprintf("%t", defaultIPAliasesEnabled), } // panicFc is a reference to panic(), it's overridden in unit tests. panicFc = panicWrapper // cfgMu protects the initialization and retrieval of config instance. cfgMu sync.RWMutex ) const ( // defaultConfigTemplate is the default configuration template for the // configuration sections. defaultConfigTemplate = ` [Core] cloud_logging_enabled = true log_level = 3 log_verbosity = 0 log_file = on_demand_plugins = true acs_client = true [Accounts] deprovision_remove = false gpasswd_add_cmd = gpasswd -a {user} {group} gpasswd_remove_cmd = gpasswd -d {user} {group} groupadd_cmd = groupadd {group} groups = adm,dip,docker,lxd,plugdev,video reuse_homedir = false useradd_cmd = useradd -m -s /bin/bash -p * {user} userdel_cmd = userdel -r {user} [Daemons] accounts_daemon = true clock_skew_daemon = true network_daemon = true [IpForwarding] ethernet_proto_id = 66 ip_aliases = {{.ipAliasesEnabled}} target_instance_ips = true [Instance] instance_id = instance_id_dir = {{.instanceIDFile}} [InstanceSetup] host_key_dir = /etc/ssh host_key_types = ecdsa,ed25519,rsa network_enabled = true optimize_local_ssd = true set_boto_config = true set_host_keys = true set_multiqueue = true [MetadataScripts] default_shell = /bin/bash run_dir = shutdown = true shutdown-windows = true startup = true startup-windows = true sysprep-specialize = true [NetworkInterfaces] dhcp_command = ip_forwarding = true setup = true [OSLogin] cert_authentication = true [MDS] mtls_bootstrapping_enabled = true cacertificates_update_enabled = true [Snapshots] enabled = false snapshot_service_ip = 169.254.169.254 snapshot_service_port = 8081 timeout_in_seconds = 60 [PluginConfig] socket_connections_dir = {{.socketConnectionsDir}} state_dir = {{.baseStateDir}} [ACS] endpoint = channel_id = host = client_debug_logging = false [Unstable] command_monitor_enabled = false command_pipe_mode = 0770 command_pipe_path = {{.commandPipe}} command_pipe_group = command_request_timeout = 10s vlan_setup_enabled = false systemd_config_dir = /usr/lib/systemd/network fqdn_address_interface_index = 0 ` ) // Sections encapsulates all the configuration sections. type Sections struct { // Core defines the core guest-agent's configuration entries/keys. Core *Core `ini:"Core,omitempty"` // AccountManager defines the address management configurations. It takes // precedence over instance's and project's metadata configuration. The // default configuration doesn't define values to it, if the user has defined // it then we shouldn't even consider metadata values. Users must check if // this pointer is nil or not. AccountManager *AccountManager `ini:"accountManager,omitempty"` // Accounts defines the non windows account management options, behaviors and // commands. Accounts *Accounts `ini:"Accounts,omitempty"` // AddressManager defines the address management configurations. It takes // precedence over instance's and project's metadata configuration. The // default configuration doesn't define values to it, if the user has defined // it then we shouldn't even consider metadata values. Users must check if // this pointer is nil or not. AddressManager *AddressManager `ini:"addressManager,omitempty"` // Daemons defines the availability of clock skew, network and account managers. Daemons *Daemons `ini:"Daemons,omitempty"` // Diagnostics defines the diagnostics configurations. It takes precedence // over instance's and project's metadata configuration. The default // configuration doesn't define values to it, if the user has defined it then // we shouldn't even consider metadata values. Users must check if this // pointer is nil or not. Diagnostics *Diagnostics `ini:"diagnostics,omitempty"` // IPForwarding defines the ip forwarding configuration options. IPForwarding *IPForwarding `ini:"IpForwarding,omitempty"` // Instance defines the instance ID handling behaviors, i.e. where to read the // ID from etc. Instance *Instance `ini:"Instance,omitempty"` // InstanceSetup defines options to basic instance setup options i.e. optimize // local ssd, network, // host keys etc. InstanceSetup *InstanceSetup `ini:"InstanceSetup,omitempty"` // MetadataScripts contains the configurations of the metadata-scripts service. MetadataScripts *MetadataScripts `ini:"MetadataScripts,omitempty"` // NetworkInterfaces defines if the network interfaces should be managed or // configured by guest-agent as well as the commands definitions for network // configuration. NetworkInterfaces *NetworkInterfaces `ini:"NetworkInterfaces,omitempty"` // OSLogin defines the OS Login configuration options. OSLogin *OSLogin `ini:"OSLogin,omitempty"` // MDS defines the MDS configuration options. MDS *MDS `ini:"MDS,omitempty"` // Snapshots defines the snapshot listener configuration and behavior i.e. the // server address and port. Snapshots *Snapshots `ini:"Snapshots,omitempty"` // Unstable is a "under development feature flags" section. No stability or // long term support is guaranteed for any keys under this section. No // application, script or utility should rely on it. Unstable *Unstable `ini:"Unstable,omitempty"` // WSFC defines the wsfc configurations. It takes precedence over instance's // and project's metadata configuration. The default configuration doesn't // define values to it, if the user has defined it then we shouldn't even // consider metadata values. Users must check if this pointer is nil or not. WSFC *WSFC `ini:"wsfc,omitempty"` // Plugin defines the plugin configurations. Plugin *Plugin `ini:"PluginConfig,omitempty"` // ACS defines the ACS configuration options. These options are overrides // for creating client connection. ACS *ACS `ini:"ACS,omitempty"` } // ACS contains the configurations of ACS section. Following options overrides // the default values. These overrides should be used for testing only. type ACS struct { // Endpoint is the ACS endpoint to use. Endpoint string `ini:"endpoint,omitempty"` // ChannelID is the ACS channel ID to use for connections. ChannelID string `ini:"channel_id,omitempty"` // Host is the ACS host to use. Host string `ini:"host,omitempty"` // ClientDebugLogging is the ACS client debug logging. Enabling this will // enable debug logging in the ACS client library. ClientDebugLogging bool `ini:"client_debug_logging,omitempty"` } // Core contains the core configuration entries of guest agent, all // configurations not tied/specific to a subsystem are defined in here. type Core struct { // CloudLoggingEnabled config toggle controls Guest Agent cloud logger. // Disabling it will stop Guest Agent for configuring and logging to Cloud // Logging. CloudLoggingEnabled bool `ini:"cloud_logging_enabled,omitempty"` // LogLevel defines the log level of the guest-agent. The CLI's flag takes // precedence over this configuration. LogLevel int `ini:"log_level,omitempty"` // LogVerbosity defines the log verbosity of the guest-agent. The CLI's flag // takes precedence over this configuration. LogVerbosity int `ini:"log_verbosity,omitempty"` // LogFile defines the log file of the guest-agent. The CLI's flag takes // precedence over this configuration. Note that this file applies to both // guest-agent and core plugin. Since core plugin and guest agent are using // the same file, log prefix can be used to differentiate their entries in // the logs. Core Plugin use "core_plugin" as prefix and guest-agent uses // none. LogFile string `ini:"log_file,omitempty"` // OnDemandPlugins defines whether the on-demand plugins support should be // enabled. By disabling this configuration the support of on-demand plugins // is turned off entirely. OnDemandPlugins bool `ini:"on_demand_plugins,omitempty"` // ACSClient defines whether the ACS client should be enabled. // By disabling this configuration the ACS client related features including // on-demand plugins, metric collection and plugin events are turned off entirely. ACSClient bool `ini:"acs_client,omitempty"` // Version defines the version of the running binary. Its for internal use // only. Value is set dynamically when config is loaded in main. Any values // provided via config file or anything will be overridden. Version string `ini:"-"` } // AccountManager contains the configurations of AccountManager section. type AccountManager struct { Disable bool `ini:"disable,omitempty"` } // Accounts contains the configurations of Accounts section. type Accounts struct { DeprovisionRemove bool `ini:"deprovision_remove,omitempty"` GPasswdAddCmd string `ini:"gpasswd_add_cmd,omitempty"` GPasswdRemoveCmd string `ini:"gpasswd_remove_cmd,omitempty"` GroupAddCmd string `ini:"groupadd_cmd,omitempty"` Groups string `ini:"groups,omitempty"` ReuseHomedir bool `ini:"reuse_homedir,omitempty"` UserAddCmd string `ini:"useradd_cmd,omitempty"` UserDelCmd string `ini:"userdel_cmd,omitempty"` } // AddressManager contains the configuration of addressManager section. type AddressManager struct { Disable bool `ini:"disable,omitempty"` } // Daemons contains the configurations of Daemons section. type Daemons struct { AccountsDaemon bool `ini:"accounts_daemon,omitempty"` ClockSkewDaemon bool `ini:"clock_skew_daemon,omitempty"` NetworkDaemon bool `ini:"network_daemon,omitempty"` } // Diagnostics contains the configurations of Diagnostics section. type Diagnostics struct { Enable bool `ini:"enable,omitempty"` } // IPForwarding contains the configurations of IPForwarding section. type IPForwarding struct { EthernetProtoID string `ini:"ethernet_proto_id,omitempty"` IPAliases bool `ini:"ip_aliases,omitempty"` TargetInstanceIPs bool `ini:"target_instance_ips,omitempty"` } // Instance contains the configurations of Instance section. type Instance struct { // InstanceID is a backward compatible key. In the past the instance id was only // supported/setup via config file, if we can't read the instance_id file then // try honoring this configuration key. InstanceID string `ini:"instance_id,omitempty"` // InstanceIDDir defines where the instance id file should be read from. InstanceIDDir string `ini:"instance_id_dir,omitempty"` } // InstanceSetup contains the configurations of InstanceSetup section. type InstanceSetup struct { HostKeyDir string `ini:"host_key_dir,omitempty"` HostKeyTypes string `ini:"host_key_types,omitempty"` NetworkEnabled bool `ini:"network_enabled,omitempty"` OptimizeLocalSSD bool `ini:"optimize_local_ssd,omitempty"` SetBotoConfig bool `ini:"set_boto_config,omitempty"` SetHostKeys bool `ini:"set_host_keys,omitempty"` SetMultiqueue bool `ini:"set_multiqueue,omitempty"` } // MetadataScripts contains the configurations of MetadataScripts section. type MetadataScripts struct { DefaultShell string `ini:"default_shell,omitempty"` RunDir string `ini:"run_dir,omitempty"` Shutdown bool `ini:"shutdown,omitempty"` ShutdownWindows bool `ini:"shutdown-windows,omitempty"` Startup bool `ini:"startup,omitempty"` StartupWindows bool `ini:"startup-windows,omitempty"` SysprepSpecialize bool `ini:"sysprep_specialize,omitempty"` } // OSLogin contains the configurations of OSLogin section. type OSLogin struct { 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"` // UpdateCACertificatesEnabled enables/disables any updates to the CA // certificates. These updates are done using tools like // update-ca-certificates or similar. UpdateCACertificatesEnabled bool `ini:"cacertificates_update_enabled,omitempty"` } // NetworkInterfaces contains the configurations of NetworkInterfaces section. type NetworkInterfaces struct { DHCPCommand string `ini:"dhcp_command,omitempty"` IPForwarding bool `ini:"ip_forwarding,omitempty"` Setup bool `ini:"setup,omitempty"` ManagePrimaryNIC bool `ini:"manage_primary_nic,omitempty"` } // Snapshots contains the configurations of Snapshots section. type Snapshots struct { Enabled bool `ini:"enabled,omitempty"` SnapshotServiceIP string `ini:"snapshot_service_ip,omitempty"` SnapshotServicePort int `ini:"snapshot_service_port,omitempty"` TimeoutInSeconds int `ini:"timeout_in_seconds,omitempty"` } // Plugin contains the configurations of Plugin section. type Plugin struct { // SocketConnectionsDir defines the directory path where plugin socket // connections file should be stored. SocketConnectionsDir string `ini:"socket_connections_dir,omitempty"` // StateDir defines the directory path where all state files should be stored. StateDir string `ini:"state_dir,omitempty"` } // Unstable contains the configurations of Unstable section. No long term // stability or support 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 { CommandMonitorEnabled bool `ini:"command_monitor_enabled,omitempty"` // CommandPipePath defines where command monitor pipe lives. On Linux this is // a path to a directory under which every Guest Agent user will create a // socket whereas on windows its a prefix which gets appended by each user's // unique identifier. CommandPipePath string `ini:"command_pipe_path,omitempty"` CommandRequestTimeout string `ini:"command_request_timeout,omitempty"` // Note that CommandPipeMode and CommandPipeGroup are ignored on Windows. // On Windows, members of Administrators can access the pipe using [ggactl]. CommandPipeMode string `ini:"command_pipe_mode,omitempty"` CommandPipeGroup string `ini:"command_pipe_group,omitempty"` VlanSetupEnabled bool `ini:"vlan_setup_enabled,omitempty"` SystemdConfigDir string `ini:"systemd_config_dir,omitempty"` SetHostname bool `ini:"set_hostname,omitempty"` SetFQDN bool `ini:"set_fqdn,omitempty"` FQDNAsHostname bool `ini:"fqdn_as_hostname,omitempty"` AdditionalAliases string `ini:"additional_aliases,omitempty"` FQDNAddressInterfaceIndex int `ini:"fqdn_address_interface_index,omitempty"` } // WSFC contains the configurations of WSFC section. type WSFC struct { Addresses string `ini:"addresses,omitempty"` Enable bool `ini:"enable,omitempty"` Port string `ini:"port,omitempty"` } // panicWrapper is a wrapper over panic() to make it testable. func panicWrapper(args ...any) { panic(args) } func applyTemplate(templateStr string, data map[string]string, buffer io.Writer) error { t, err := template.New("").Parse(templateStr) if err != nil { return fmt.Errorf("failed to parse template: %w", err) } err = t.Execute(buffer, data) if err != nil { return fmt.Errorf("failed to execute template: %w", err) } return nil } func defaultDataSources(extraDefaults []byte) []any { var res []any if len(extraDefaults) > 0 { res = append(res, extraDefaults) } return append(res, []any{ defaultConfigFile, defaultConfigFile + ".distro", defaultConfigFile + ".template", }...) } // Load loads default configuration and the configuration from default config files. func Load(extraDefaults []byte) error { cfgMu.Lock() defer cfgMu.Unlock() opts := ini.LoadOptions{ Loose: true, Insensitive: true, } var buffer bytes.Buffer err := applyTemplate(defaultConfigTemplate, defaultConfigValues, &buffer) if err != nil { return fmt.Errorf("unable to apply %v to config template: %w", defaultConfigValues, err) } sources := dataSources(extraDefaults) cfg, err := ini.LoadSources(opts, buffer.Bytes(), sources...) if err != nil { return fmt.Errorf("failed to load configuration: %+w", err) } sections := new(Sections) if err := cfg.MapTo(sections); err != nil { return fmt.Errorf("failed to map configuration to object: %w", err) } instance = sections return nil } // Retrieve returns the configuration's instance previously loaded with Load(). func Retrieve() *Sections { cfgMu.RLock() defer cfgMu.RUnlock() if instance == nil { panicFc("cfg package was not initialized, Load() should be called in the early initialization code path") } return instance }