blob: 1f5254fb499b856bbb6cbcfe01eb2eaee658ebb8 [file] [log] [blame]
// 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
}