blob: 440607bba25e07ed9984bff32c5687b2b1ec2834 [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
// Utility functions for manipulating device policy.
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"io/ioutil"
"os"
"time"
"policy-manager/pkg/sysapi"
pmpb "policy-manager/protos"
pb "policy-manager/third_party/chromium/devicepolicyproto"
"github.com/golang/protobuf/proto"
)
// DevicePolicy is an opaque type that represents the device policy. This hides
// the complexity of the device policy protobufs from the users.
type DevicePolicy struct {
metricsEnabled *pb.MetricsEnabledProto
// InstanceConfig is an InstanceConfigProto that manages the device policy file.
instanceConfig *pmpb.InstanceConfig
}
// hashPolicyData hashes the given policy data using SHA1.
func hashPolicyData(data []byte) []byte {
h := sha1.New()
h.Write(data)
return h.Sum(nil)
}
// generateDevicePolicy generates a signed PolicyFetchResponse protobuf that can
// be written to disk. The provided private key will be used to generate the
// signature.
func (p *DevicePolicy) generateDevicePolicy(key *rsa.PrivateKey) (*pb.PolicyFetchResponse, error) {
// Generate ChromeDeviceSettingsProto.
chromeDeviceSettings := pb.ChromeDeviceSettingsProto{
MetricsEnabled: p.metricsEnabled,
}
chromeDeviceSettingsBytes, err := proto.Marshal(&chromeDeviceSettings)
if err != nil {
return nil, err
}
// Generate PolicyData.
policyData := pb.PolicyData{
PolicyType: proto.String(policyType),
Timestamp: proto.Int64(time.Now().Unix()),
PolicyValue: chromeDeviceSettingsBytes,
PublicKeyVersion: proto.Int32(1),
}
policyDataBytes, err := proto.Marshal(&policyData)
if err != nil {
return nil, err
}
// Hash policyData.
hash := hashPolicyData(policyDataBytes)
// Sign PolicyData.
policyDataSig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA1, hash)
if err != nil {
return nil, err
}
// Generate PolicyFetchResponse.
policyFetchResponse := &pb.PolicyFetchResponse{
PolicyData: policyDataBytes,
PolicyDataSignature: policyDataSig,
}
return policyFetchResponse, nil
}
// SetFromInstanceConfig sets the settings from the instance config to the
// DevicePolicy.
// Returns 'true' if this resulted in any change in device policy, otherwise false.
func (p *DevicePolicy) SetFromInstanceConfig(config *pmpb.InstanceConfig) bool {
if config == nil {
return false
}
policyChanged := false
instanceConfig := &pmpb.InstanceConfig{
UpdateStrategy: proto.String(config.GetUpdateStrategy()),
MetricsEnabled: proto.Bool(config.GetMetricsEnabled()),
HealthMonitorConfig: proto.Clone(config.HealthMonitorConfig).(*pmpb.HealthMonitorConfig),
}
metricsEnabled := &pb.MetricsEnabledProto{
MetricsEnabled: proto.Bool(config.GetMetricsEnabled()),
}
if p.instanceConfig == nil || !proto.Equal(instanceConfig, p.instanceConfig) {
policyChanged = true
}
p.instanceConfig = instanceConfig
p.metricsEnabled = metricsEnabled
return policyChanged
}
// ToInstanceConfig converts the DevicePolicy into InstanceConfig proto
func (p *DevicePolicy) ToInstanceConfig(config *pmpb.InstanceConfig) {
if config == nil {
return
}
if p.instanceConfig != nil {
config.UpdateStrategy = proto.String(p.instanceConfig.GetUpdateStrategy())
config.MetricsEnabled = proto.Bool(p.instanceConfig.GetMetricsEnabled())
config.HealthMonitorConfig = proto.Clone(p.instanceConfig.HealthMonitorConfig).(*pmpb.HealthMonitorConfig)
}
}
// WriteFile writes the policy to disk. The policy will atomically replace the existing policy.
func (p *DevicePolicy) SignAndWrite(key *rsa.PrivateKey, devicePolicyFile string, instanceConfigFile string) error {
// Generate policy protobuf.
policyFetchResponse, err := p.generateDevicePolicy(key)
if err != nil {
return nil
}
policyBytes, err := proto.Marshal(policyFetchResponse)
if err != nil {
return err
}
// Generate policy protobuf.
instanceConfigBytes, err := proto.Marshal(p.instanceConfig)
if err != nil {
return err
}
// Write the policy file atomically.
if err := sysapi.AtomicWriteFile(devicePolicyFile, policyBytes, configFilePerm); err != nil {
return err
}
// Write the policy file atomically.
if err := sysapi.AtomicWriteFile(instanceConfigFile, instanceConfigBytes, configFilePerm); err != nil {
return err
}
return nil
}
// ReadPolicyFromDisk reads the device policy from disk.
// No error is returned for missing policy file.
func (p *DevicePolicy) ReadPolicyFromDisk(instanceConfigFile string) error {
// Check if cos device policy file exists
if _, err := os.Stat(instanceConfigFile); os.IsNotExist(err) {
return nil
}
// Read the policy file and convert it to protobuf format.
instanceConfigBytes, err := ioutil.ReadFile(instanceConfigFile)
if err != nil {
return err
}
// Unmarshal InstanceConfig
var instanceConfig pmpb.InstanceConfig
err = proto.Unmarshal(instanceConfigBytes, &instanceConfig)
if err != nil {
return err
}
// Store the relevant protobufs in memory.
p.instanceConfig = &instanceConfig
return nil
}