| // 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 devicepolicy |
| |
| // Implementation of the Manager interface for managing Chrome OS device policy. |
| // For more detail on Chrome OS device policy, see |
| // http://www.chromium.org/developers/how-tos/enterprise/protobuf-encoded-policy-blobs |
| |
| import ( |
| "crypto/rand" |
| "crypto/rsa" |
| "crypto/x509" |
| "errors" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| pmpb "policy-manager/protos" |
| "reflect" |
| |
| "github.com/golang/glog" |
| "github.com/golang/protobuf/proto" |
| ) |
| |
| const ( |
| // policyType is a Chrome OS constant to specify that the policy data |
| // refers to a device policy. |
| // See line 2 in third_party/chromium/devicepolicyproto/device_management_backend.proto. |
| policyType = "google/chromeos/device" |
| |
| // configFilePerm is the permission for the COS policy file. |
| configFilePerm = os.FileMode(0644) |
| |
| // keyFilePerm is the permission for public/private key files. |
| keyFilePerm = os.FileMode(0600) |
| |
| // rsaKeyBits is the number of bits to use to generate the private key. |
| rsaKeyBits = 1024 |
| ) |
| |
| // createKeyFiles creates a new public and private key pair and writes them to |
| // disk. |
| // Note that when the key files are being created, any existing policy cannot be |
| // read and verified by Chrome OS components such as update_engine. |
| func createKeyFiles(publicKeyFile string, privateKeyFile string) (*rsa.PrivateKey, error) { |
| // Generate RSA key pair. |
| key, err := rsa.GenerateKey(rand.Reader, rsaKeyBits) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Serialize the public key to DER-encoded PKIX format and output to |
| // file. |
| publicKeyBytes, err := x509.MarshalPKIXPublicKey(key.Public()) |
| if err != nil { |
| return nil, err |
| } |
| err = ioutil.WriteFile(publicKeyFile, publicKeyBytes, keyFilePerm) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Serialize the private key to ASN.1 DER encoded form and output to |
| // file. |
| privateKeyBytes := x509.MarshalPKCS1PrivateKey(key) |
| if err != nil { |
| return nil, err |
| } |
| err = ioutil.WriteFile(privateKeyFile, privateKeyBytes, keyFilePerm) |
| if err != nil { |
| return nil, err |
| } |
| |
| glog.V(1).Info("new keys created") |
| return key, nil |
| } |
| |
| // checkKeyFiles checks the private/public key files are present on disk and |
| // form a valid key pair. |
| // An error is returned if the private or public key file is not present or if |
| // the keys don't match. |
| // If no error, then the private key is returned. |
| func checkKeyFiles(publicKeyFile string, privateKeyFile string) (*rsa.PrivateKey, error) { |
| // Get private key. |
| privateKeyBytes, err := ioutil.ReadFile(privateKeyFile) |
| if err != nil { |
| return nil, err |
| } |
| privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Get public key. |
| publicKeyBytes, err := ioutil.ReadFile(publicKeyFile) |
| if err != nil { |
| return nil, err |
| } |
| publicKey, err := x509.ParsePKIXPublicKey(publicKeyBytes) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Verify that the public and private keys match. |
| if !reflect.DeepEqual(publicKey, privateKey.Public()) { |
| return nil, errors.New("public and private keys don't match") |
| } |
| |
| return privateKey, nil |
| } |
| |
| // getKeys returns the private key of the public/private key pair present on |
| // disk. If no valid key pair exsits, an error will be returned unless the |
| // caller has set generateIfMissing to true. |
| func getKeys(publicKeyFile string, privateKeyFile string, generateIfMissing bool) (*rsa.PrivateKey, error) { |
| // generateKeysIfNeeded is called when there is no valid key pair on |
| // disk and should consult the generateIfMissing flag to decide wheher |
| // new keys should be generated. |
| generateKeysIfNeeded := func() (*rsa.PrivateKey, error) { |
| if generateIfMissing { |
| return createKeyFiles(publicKeyFile, privateKeyFile) |
| } |
| return nil, errors.New("missing key files") |
| } |
| |
| // Check if valid public/private key files are present. |
| _, errPrivateMissing := os.Stat(privateKeyFile) |
| _, errPublicMissing := os.Stat(publicKeyFile) |
| |
| if os.IsNotExist(errPrivateMissing) || os.IsNotExist(errPublicMissing) { |
| glog.V(1).Info("keys missing") |
| return generateKeysIfNeeded() |
| } else if privateKey, err := checkKeyFiles(publicKeyFile, privateKeyFile); err != nil { |
| // The keys on disk do not form a valid private/public key pair. |
| glog.V(1).Infof("invalid key pair: %v", err) |
| return generateKeysIfNeeded() |
| } else { |
| return privateKey, nil |
| } |
| } |
| |
| // GetDefaultInstanceConfigFromBase returns the instance config derived from given |
| // baseConfig. All missing fields in baseConfig are populated with default values. |
| func GetDefaultInstanceConfigFromBase(baseConfig *pmpb.InstanceConfig) *pmpb.InstanceConfig { |
| // default policy allows all updates, disables cos monitoring logging and monitoring. |
| result := &pmpb.InstanceConfig{ |
| MetricsEnabled: proto.Bool(false), |
| UpdateStrategy: proto.String(""), |
| HealthMonitorConfig: &pmpb.HealthMonitorConfig{ |
| Enforced: proto.Bool(false), |
| LoggingEnabled: proto.Bool(false), |
| MonitoringEnabled: proto.Bool(false), |
| }, |
| } |
| |
| // Override fields from baseConfig. |
| proto.Merge(result, baseConfig) |
| return result |
| } |
| |
| // InitDevicePolicy initializes instance's environment to support device policy. |
| // The initialization process includes (in order): |
| // 1. Create the directory to store policy file if it doesn't exist. |
| // 2. New devicepolicy will be created and written to disk (overwriting any previous |
| // settings), All missing fields will have default values. If the given InstanceConfig |
| // is valid (not nil), new devicepolicy will be generated from it; if the given |
| // InstanceConfig is nil, which indicates metadata server fetching error, new devicepolicy |
| // will be generated from current devicepolicy, which is stored on disk. |
| func InitDevicePolicy(baseConfig *pmpb.InstanceConfig, devicePolicyFile string, instanceConfigFile string, publicKeyFile string, privateKeyFile string) error { |
| // Make sure the policy directory exists. |
| dir := filepath.Dir(instanceConfigFile) |
| if err := os.MkdirAll(dir, configFilePerm); err != nil { |
| return err |
| } |
| |
| // Create new keys if no valid keys are present. |
| _, err := getKeys(publicKeyFile, privateKeyFile, true) |
| if err != nil { |
| return err |
| } |
| |
| onDiskConfig, err := GetInstanceConfig(instanceConfigFile) |
| // If failed to fetch config from metadata server, use the copy on disk. |
| if baseConfig == nil && err == nil { |
| glog.V(1).Infof("Reusing existing device policy for initialization: %v", onDiskConfig) |
| return SetInstanceConfig(onDiskConfig, devicePolicyFile, instanceConfigFile, publicKeyFile, privateKeyFile) |
| } |
| |
| // Overwrite the copy of device policy on disk using baseConfig. |
| return SetInstanceConfig(baseConfig, devicePolicyFile, instanceConfigFile, publicKeyFile, privateKeyFile) |
| } |
| |
| // SetInstanceConfig sets a new device policy from an InstanceConfig. The |
| // new policy will atomically replace the existing policy. |
| func SetInstanceConfig(baseConfig *pmpb.InstanceConfig, devicePolicyFile string, instanceConfigFile string, publicKeyFile string, privateKeyFile string) error { |
| // We need to populate unspecified fields with default values. |
| config := GetDefaultInstanceConfigFromBase(baseConfig) |
| |
| // Get the private key from disk. Don't generate new keys if valid |
| // public/private key pair is missing. |
| privateKey, err := getKeys(publicKeyFile, privateKeyFile, false) |
| if err != nil { |
| return err |
| } |
| |
| // Read the current device policy. Ignore error if policy file is missing. |
| policy := new(DevicePolicy) |
| if err := policy.ReadPolicyFromDisk(instanceConfigFile); err != nil { |
| return err |
| } |
| |
| currentConfig := new(pmpb.InstanceConfig) |
| policy.ToInstanceConfig(currentConfig) |
| |
| // Set the device policy based on new config. |
| policyChanged := policy.SetFromInstanceConfig(config) |
| if !policyChanged { |
| glog.Infof("Skipping device policy update. Current: {%+v}", currentConfig) |
| return nil |
| } else { |
| newConfig := new(pmpb.InstanceConfig) |
| policy.ToInstanceConfig(newConfig) |
| glog.Infof("Updating cos device policy. Instance config changed from {%+v} to {%+v}", |
| currentConfig, newConfig) |
| } |
| |
| return policy.SignAndWrite(privateKey, devicePolicyFile, instanceConfigFile) |
| } |
| |
| func GetInstanceConfig(instanceConfigFile string) (config *pmpb.InstanceConfig, err error) { |
| // Read the current device policy. |
| policy := new(DevicePolicy) |
| err = policy.ReadPolicyFromDisk(instanceConfigFile) |
| if err != nil { |
| return nil, err |
| } |
| |
| config = new(pmpb.InstanceConfig) |
| policy.ToInstanceConfig(config) |
| |
| return config, nil |
| } |