| // 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. |
| |
| package monitor |
| |
| import ( |
| "errors" |
| "time" |
| |
| "policy-manager/pkg/configfetcher" |
| "policy-manager/pkg/devicepolicy" |
| "policy-manager/pkg/policyenforcer" |
| "policy-manager/pkg/sysapi" |
| "policy-manager/pkg/systemd" |
| "policy-manager/protos" |
| |
| "github.com/golang/glog" |
| |
| "github.com/golang/protobuf/proto" |
| ) |
| |
| const ( |
| // instanceConfigFile is the file that stores the config related to |
| // policy manager. |
| instanceConfigFile = "/var/lib/devicesettings/instance_config" |
| |
| // devicePolicyFile is the default file of the device policy. This file is |
| // used by crash-sender service and update-engine serivice to get any |
| // config related to them. |
| devicePolicyFile = "/var/lib/devicesettings/policy" |
| |
| // publicKeyFile is the file containing the public key that will be used |
| // to verify the signature on the device policy file. |
| // The key will be encoded using DER-encoded PKIX format. |
| // Note that the file name is decided by Chrome OS. |
| publicKeyFile = "/var/lib/devicesettings/owner.key" |
| |
| // privateKeyFile is the file containing the private key that will be |
| // used to sign the policy file. |
| // The key will be encoded using ASN.1 DER encoded form. |
| // Note that we are signing the policy file locally since we do not want |
| // to do any key management on the server side. |
| privateKeyFile = "/var/lib/devicesettings/private.key" |
| |
| // GCEMetadataURL is the URL of metadata server where we can query |
| // metadata tags and their values. |
| gceMetadataURL = "http://metadata.google.internal/computeMetadata/v1" |
| |
| systemctlCmd = "systemctl" |
| ) |
| |
| var serviceMonitor = map[string]string{ |
| "loggingService": "logging-agent.target", |
| "monitoringService": "node-problem-detector.service", |
| "metricsService": "crash-reporter.service", |
| "updateService": "update-engine.service", |
| } |
| |
| // resolveEnforcementConfig sets enforcement related fields in userConfig to |
| // true if either logging or monitoring feature is enabled, or if the device |
| // policy file has the corresponding enforcement option set to true. |
| // If failed to find/parse the device policy file, simply assumes the enforcement |
| // options are set to false on disk. |
| func resolveEnforcementConfig(userConfig *protos.InstanceConfig) error { |
| if userConfig == nil { |
| return errors.New("value of userConfig is nil") |
| } |
| |
| // Set HealthMonitorConfig.Enforced to false as default value. |
| if userConfig.HealthMonitorConfig == nil { |
| userConfig.HealthMonitorConfig = new(protos.HealthMonitorConfig) |
| } |
| userConfig.HealthMonitorConfig.Enforced = proto.Bool(false) |
| |
| // Set HealthMonitorConfig.Enforced to true if either logging or monitoring is enabled and return. |
| if userConfig.HealthMonitorConfig.GetLoggingEnabled() || userConfig.HealthMonitorConfig.GetMonitoringEnabled() { |
| userConfig.HealthMonitorConfig.Enforced = proto.Bool(true) |
| return nil |
| } |
| |
| // Read the existing state on disk. |
| onDiskConfig, err := devicepolicy.GetInstanceConfig(instanceConfigFile) |
| if err != nil { |
| return err |
| } |
| |
| // userConfig should respect onDiskConfig when both logging and monitoring are disabled. |
| if onDiskConfig != nil && onDiskConfig.HealthMonitorConfig != nil { |
| userConfig.HealthMonitorConfig.Enforced = proto.Bool(onDiskConfig.HealthMonitorConfig.GetEnforced()) |
| } |
| return nil |
| } |
| |
| // updateInstanceConfig queries the API (if needed) for new instance config and |
| // changes the device policy stored in the policy file. userConfig is what we |
| // got from GCE metadata (it can even be nil). The API query to backend is not |
| // made if the given userConfig can be locally understood by Policy Manager. |
| // Note that the given devicepolicy.Manager should be initialized. |
| func updateInstanceConfig(userConfig *protos.InstanceConfig) error { |
| if err := resolveEnforcementConfig(userConfig); err != nil { |
| return err |
| } |
| return devicepolicy.SetInstanceConfig(userConfig, devicePolicyFile, instanceConfigFile, publicKeyFile, privateKeyFile) |
| } |
| |
| // initDevicePolicyOrDie initializes the device policy. It's success ensures that the default |
| // devicepolicy file and all required keys to access it are present and consistent. If |
| // something fails, this function logs the error message and exists the process. |
| func initDevicePolicyOrDie() error { |
| initConfig := &protos.InstanceConfig{ |
| MetricsEnabled: proto.Bool(false), |
| UpdateStrategy: proto.String(""), |
| HealthMonitorConfig: &protos.HealthMonitorConfig{ |
| Enforced: proto.Bool(false), |
| LoggingEnabled: proto.Bool(false), |
| MonitoringEnabled: proto.Bool(false), |
| }, |
| } |
| |
| if err := resolveEnforcementConfig(initConfig); err != nil { |
| return err |
| } |
| |
| // Stopping the crash-reporter service. When GCE instance boots, crash-reporter service |
| // is in `Activating` state due to which it is returning error and we don't |
| // need this service until cos-metrics-enabled is set to true. |
| systemdClient := systemd.NewSystemdClient(systemctlCmd) |
| if err := systemdClient.StopUnit(serviceMonitor["metricsService"]); err != nil { |
| return err |
| } |
| |
| return devicepolicy.InitDevicePolicy(initConfig, devicePolicyFile, instanceConfigFile, publicKeyFile, privateKeyFile) |
| } |
| |
| // InitDevicePolicy is the handler function for initializing the device policy. |
| func InitDevicePolicy() { |
| glog.Info("Starting device policy initialization...") |
| |
| if err := initDevicePolicyOrDie(); err != nil { |
| glog.Exitf("Error initializing device policy: %v", err) |
| } |
| |
| glog.Info("Device policy initialized successfully!") |
| } |
| |
| // HandleMonitorCmd is the handler function for running Policy Manager |
| // in monitor mode, which watches the metadata for new update configuration |
| // and periodically sends the status updates. |
| func HandleMonitorCmd() { |
| glog.Info("Started in monitor mode") |
| |
| // Send notify command to notify systemd if initialization was successful |
| if _, _, err := sysapi.RunCommand("systemd-notify", "--ready", "--status='Initialization was successful'"); err != nil { |
| glog.Errorf("error in notifying systemd: %s\n", err) |
| } |
| |
| // Get instance ID. |
| var instanceID uint64 |
| for { |
| id, err := configfetcher.GetInstanceID() |
| if err == nil { |
| instanceID = id |
| break |
| } |
| glog.Errorf("error while fetching instance id: %v", err) |
| time.Sleep(1 * time.Minute) |
| } |
| glog.Infof("Detected instance ID is: %d", instanceID) |
| |
| initDevicePolicyOrDie() |
| systemdClient := systemd.NewSystemdClient(systemctlCmd) |
| policyEnforcer := policyenforcer.NewPolicyEnforcer(*systemdClient) |
| |
| userConfig := make(chan *protos.InstanceConfig) |
| go configfetcher.PollUserConfig(userConfig, gceMetadataURL) |
| |
| for { |
| instanceConfig := <-userConfig |
| // userConfigUpdate returns the latest value of user config when the metadata are updated. |
| glog.Infof("Using InstanceConfig: %v", instanceConfig) |
| |
| if err := updateInstanceConfig(instanceConfig); err != nil { |
| glog.Warning(err) |
| } |
| if err := policyEnforcer.UpdateServiceState(instanceConfigFile, serviceMonitor); err != nil { |
| glog.Errorf("error while updating instance: %v", err) |
| } |
| } |
| } |