blob: d3ba701d307ebd928502cd7d98fd36c7c15a2fa4 [file] [log] [blame] [edit]
// 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
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"os"
"path/filepath"
"reflect"
"testing"
"policy_manager/mock/mocksysapi"
pb "policy_manager/policymanagerproto"
"github.com/golang/mock/gomock"
"github.com/golang/protobuf/proto"
)
var instanceConfig = &pb.InstanceConfig{
MetricsEnabled: proto.Bool(true),
TargetVersionPrefix: proto.String("7099."),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(0),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(true),
MonitoringEnabled: proto.Bool(false),
},
}
// generateFakePolicyBytes is a helper function to generate device policy bytes
// to simulate how device policy will be read from disk.
func generateFakePolicyBytes(t *testing.T, key *rsa.PrivateKey, config *pb.InstanceConfig) (policyBytes []byte, cosDevicePolicyBytes []byte) {
// Create mock policy.
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0)).AnyTimes()
policy := new(DevicePolicy)
policy.SetFromInstanceConfig(config)
policyFetchResponse, err := policy.generateDevicePolicy(key, mockAPI)
if err != nil {
t.Fatal(err)
}
policyBytes, err = proto.Marshal(policyFetchResponse)
if err != nil {
t.Fatal(err)
}
cosDevicePolicyBytes, err = proto.Marshal(policy.cosDevicePolicy)
if err != nil {
t.Fatal(err)
}
return policyBytes, cosDevicePolicyBytes
}
func genInterceptPolicy(t *testing.T, expectBytes []byte) func(string, []byte, os.FileMode) {
return func(_ string, content []byte, _ os.FileMode) {
if !reflect.DeepEqual(expectBytes, content) {
t.Errorf("In test %v, got policy: %v, expect: %v", t.Name(), content, expectBytes)
}
}
}
// TestCreateKeyFiles tests that the function is writing to the keys to the
// correct files with the right permissions.
func TestCreateKeyFiles(t *testing.T) {
// Set up gomock controller.
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
mockAPI.EXPECT().WriteFile(publicKeyFile, gomock.Any(), keyFilePerm).
Return(nil)
mockAPI.EXPECT().WriteFile(privateKeyFile, gomock.Any(), keyFilePerm).
Return(nil)
if _, err := createKeyFiles(mockAPI); err != nil {
t.Error(err)
}
}
// TestCheckKeyFiles tests that the checkKeyFiles function checks for the
// existence of both the private and public key files as well as whether the
// public and private keys match.
func TestCheckKeyFiles(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
privateKey2, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public()
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyBytes2 := x509.MarshalPKCS1PrivateKey(privateKey2)
tests := []struct {
name string
publicKeyBytes []byte
privateKeyBytes []byte
privateKey *rsa.PrivateKey
expectErr bool
}{
{
"Good Key Pair",
publicKeyBytes,
privateKeyBytes,
privateKey,
false,
},
{
"Bad Public Key Encoding",
[]byte("abc"),
privateKeyBytes,
nil,
true,
},
{
"Bad Private Key Encoding",
publicKeyBytes,
[]byte("abc"),
nil,
true,
},
{
"Non-matching Key Pair",
publicKeyBytes,
privateKeyBytes2,
nil,
true,
},
}
for _, test := range tests {
mockCtrl := gomock.NewController(t)
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
mockAPI.EXPECT().ReadFile(publicKeyFile).
Return(test.publicKeyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(privateKeyFile).
Return(test.privateKeyBytes, nil).AnyTimes()
privateKeyResult, err := checkKeyFiles(mockAPI)
if err == nil {
if test.expectErr {
t.Errorf("test %s passed, expect error",
test.name)
} else if !reflect.DeepEqual(privateKeyResult, test.privateKey) {
t.Errorf("test %s got private key %v, expect %v",
test.name, privateKeyResult, test.privateKey)
}
} else {
if !test.expectErr {
t.Errorf("test %s got error %v, expect to pass",
test.name, err)
}
}
mockCtrl.Finish()
}
}
// TestInitDevicePolicyCreateKeyFiles tests that InitDevicePolicy() creates
// new key files if key files if they are not present.
func TestInitDevicePolicyCreateKeyFiles(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Make sure the initialization process ensures that the policy
// directory is present.
policyDir := filepath.Dir(policyFile)
mockAPI.EXPECT().MkdirAll(policyDir, policyFilePerm).Return(nil)
// Create mock private/public key.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
// The initialization process should create new key files if they don't
// exist and read them back for writing the default policy file.
privateNotExist := mockAPI.EXPECT().FileExists(privateKeyFile).Return(false).AnyTimes()
writeToPrivate := mockAPI.EXPECT().WriteFile(privateKeyFile, gomock.Any(), keyFilePerm).Return(nil).After(privateNotExist)
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true).AnyTimes().After(writeToPrivate)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil).AnyTimes().After(writeToPrivate)
publicNotExist := mockAPI.EXPECT().FileExists(publicKeyFile).Return(false).AnyTimes()
writeToPublic := mockAPI.EXPECT().WriteFile(publicKeyFile, gomock.Any(), keyFilePerm).Return(nil).After(publicNotExist)
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true).AnyTimes().After(writeToPublic)
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil).AnyTimes().After(writeToPublic)
// Initialization process should create two default policy files.
gomock.InOrder(
mockAPI.EXPECT().FileExists(policyFile).Return(false).AnyTimes(),
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0)),
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(), policyFilePerm).Return(nil),
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes(),
)
gomock.InOrder(
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(false).AnyTimes(),
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(), cosDevicePolicyFilePerm).Return(nil),
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true).AnyTimes(),
)
manager := NewManager(mockAPI)
if err := manager.InitDevicePolicy(nil); err != nil {
t.Error(err)
}
}
// TestInitDevicePolicyCheckKeyFiles tests that InitDevicePolicy() checks the
// content of key files and creates new key files if the keys are not valid.
func TestInitDevicePolicyCheckKeyFiles(t *testing.T) {
// Create mock private/public key.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Make sure the initialization process ensures that the policy
// directory is present.
policyDir := filepath.Dir(policyFile)
mockAPI.EXPECT().MkdirAll(policyDir, policyFilePerm).Return(nil)
// The initialization process should create new key files if the
// existing ones are not valid.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true).AnyTimes()
// Return invalid public key data.
mockAPI.EXPECT().ReadFile(publicKeyFile).Return([]byte("abc"), nil)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil).
AnyTimes()
// The initialization process should recreate the key files.
mockAPI.EXPECT().WriteFile(publicKeyFile, gomock.Any(), keyFilePerm).
Return(nil)
mockAPI.EXPECT().WriteFile(privateKeyFile, gomock.Any(), keyFilePerm).
Return(nil)
// Return the correct public key data this time.
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil).AnyTimes()
// Initialization process should create two default policy files.
gomock.InOrder(
mockAPI.EXPECT().FileExists(policyFile).Return(false).AnyTimes(),
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0)),
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(), policyFilePerm).Return(nil),
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes(),
)
gomock.InOrder(
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(false).AnyTimes(),
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(), cosDevicePolicyFilePerm).Return(nil),
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true).AnyTimes(),
)
manager := NewManager(mockAPI)
if err := manager.InitDevicePolicy(nil); err != nil {
t.Error(err)
}
}
// TestInitDevicePolicyNoOnDiskPolicy tests the scenario where InitDevicePolicy() is called
// with a nil InstanceConfig when one of device policy file missing. In this case, both files
// will be written.
func TestInitDevicePolicyNoOnDiskPolicy(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public()
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
// Create fake policy.
policyBytes, _ := generateFakePolicyBytes(t, privateKey, instanceConfig)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Make sure the initialization process ensures that the policy
// directory is present.
policyDir := filepath.Dir(policyFile)
mockAPI.EXPECT().MkdirAll(policyDir, policyFilePerm).Return(nil)
// The initialization process should not create new key files if the
// existing ones are valid.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true).AnyTimes()
// Return valid public/private key data.
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil).AnyTimes()
expectedInstanceConfig := getDefaultInstanceConfigFromBase(&pb.InstanceConfig{})
expectedPolicyBytes, expectedCosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, expectedInstanceConfig)
// As long as one policy file is missing, both need to be written.
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0)).AnyTimes()
gomock.InOrder(
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes(),
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil).AnyTimes(),
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, expectedPolicyBytes)).Return(nil),
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes(),
mockAPI.EXPECT().ReadFile(policyFile).Return(expectedPolicyBytes, nil).AnyTimes(),
)
gomock.InOrder(
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(false).AnyTimes(),
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(),
cosDevicePolicyFilePerm).Do(genInterceptPolicy(t, expectedCosDevicePolicyBytes)).Return(nil),
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true).AnyTimes(),
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(expectedCosDevicePolicyBytes, nil).AnyTimes(),
)
manager := NewManager(mockAPI)
if err := manager.InitDevicePolicy(nil); err != nil {
t.Error(err)
}
}
// TestInitDevicePolicyValidPolicy tests the scenario where InitDevicePolicy() is called
// with a valid InstanceConfig when a device policy files already exist. In this case,
// the files are re-written.
func TestInitDevicePolicyValidPolicy(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public()
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
// Create fake policy.
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, instanceConfig)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Make sure the initialization process ensures that the policy
// directory is present.
policyDir := filepath.Dir(policyFile)
mockAPI.EXPECT().MkdirAll(policyDir, policyFilePerm).Return(nil)
// The initialization process should not create new key files if the
// existing ones are valid.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true).AnyTimes()
// Return valid public/private key data.
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil).AnyTimes()
// Initialization process should not create default policy files if the
// existing policy files are valid.
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true).AnyTimes()
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0)).AnyTimes()
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(cosDevicePolicyBytes, nil).AnyTimes()
newInstanceConfig := getDefaultInstanceConfigFromBase(&pb.InstanceConfig{})
newPolicyBytes, newCosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, newInstanceConfig)
// Expect the policy files to be written.
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, newPolicyBytes)).Return(nil)
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, newCosDevicePolicyBytes)).Return(nil)
manager := NewManager(mockAPI)
if err := manager.InitDevicePolicy(&pb.InstanceConfig{}); err != nil {
t.Error(err)
}
}
// TestInitDevicePolicyWithBasePolicy tests the InitDevicePolicy() function when a custom
// base policy is specified.
func TestInitDevicePolicyWithBasePolicy(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public()
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
// Create fake policy.
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, instanceConfig)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Make sure the initialization process ensures that the policy
// directory is present.
policyDir := filepath.Dir(policyFile)
mockAPI.EXPECT().MkdirAll(policyDir, policyFilePerm).Return(nil)
// The initialization process should not create new key files if the
// existing ones are valid.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true).AnyTimes()
// Return valid public/private key data.
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil).AnyTimes()
// Initialization process should not create policy files if the
// existing policy files are valid.
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true).AnyTimes()
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0)).AnyTimes()
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(cosDevicePolicyBytes, nil).AnyTimes()
// Use custom base policy.
newInstanceConfig := getDefaultInstanceConfigFromBase(
&pb.InstanceConfig{
TargetVersionPrefix: proto.String("7100."),
RebootAfterUpdate: proto.Bool(true),
})
newPolicyBytes, newCosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, newInstanceConfig)
// Expect the policy to be written.
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, newPolicyBytes)).Return(nil)
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(),
cosDevicePolicyFilePerm).Do(genInterceptPolicy(t, newCosDevicePolicyBytes)).Return(nil)
manager := NewManager(mockAPI)
if err := manager.InitDevicePolicy(newInstanceConfig); err != nil {
t.Error(err)
}
}
// TestInitDevicePolicyWithoutBasePolicy tests that when baseConfig is nil but
// there are existing device policy on disk, the existing device policy should
// be populated with missing default fields and re-written to disk if changed.
func TestInitDevicePolicyWithNilBasePolicy(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public()
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
// Create mock on-disk device policy.
currentConfig := &pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String("8000."),
RebootAfterUpdate: proto.Bool(true),
UpdateScatterSeconds: proto.Int64(10),
}
currentConfig = getDefaultInstanceConfigFromBase(currentConfig)
currentPolicyBytes, currentCosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, currentConfig)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Make sure the initialization process ensures that the policy
// directory is present.
mockAPI.EXPECT().MkdirAll(filepath.Dir(policyFile), policyFilePerm).Return(nil)
// The initialization process should not create new key files if the
// existing ones are valid.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true).AnyTimes()
// Return valid public/private key data.
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil).AnyTimes()
// Return the mocked on-disk device policy.
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true).AnyTimes()
mockAPI.EXPECT().ReadFile(policyFile).Return(currentPolicyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(currentCosDevicePolicyBytes, nil).AnyTimes()
manager := NewManager(mockAPI)
if err := manager.InitDevicePolicy(nil); err != nil {
t.Error(err)
}
}
// TestInitDevicePolicyWithNilBasePolicyAndNoCosDevicePolicyOnDisk tests that when baseConfig is
// nil and there is no COS policy file on disk, both ChromeOS device policy and COS policy
// should be populated using default.
func TestInitDevicePolicyWithNilBasePolicyAndNoCosDevicePolicyOnDisk(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public()
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
// Create mock on-disk device policy.
currentConfig := &pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String("8000."),
RebootAfterUpdate: proto.Bool(true),
UpdateScatterSeconds: proto.Int64(10),
}
currentConfig = getDefaultInstanceConfigFromBase(currentConfig)
currentPolicyBytes, _ := generateFakePolicyBytes(t, privateKey, currentConfig)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Make sure the initialization process ensures that the policy
// directory is present.
mockAPI.EXPECT().MkdirAll(filepath.Dir(policyFile), policyFilePerm).Return(nil)
// The initialization process should not create new key files if the
// existing ones are valid.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true).AnyTimes()
// Return valid public/private key data.
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil).AnyTimes()
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil).AnyTimes()
// Return the mocked on-disk device policy.
mockAPI.EXPECT().FileExists(policyFile).Return(true).AnyTimes()
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(false).AnyTimes()
mockAPI.EXPECT().ReadFile(policyFile).Return(currentPolicyBytes, nil).AnyTimes()
// Expect both policy files to be written with default value.
defaultInstanceConfig := getDefaultInstanceConfigFromBase(&pb.InstanceConfig{})
defaultPolicyBytes, defaultCosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, defaultInstanceConfig)
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0)).AnyTimes()
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, defaultPolicyBytes)).Return(nil)
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(),
cosDevicePolicyFilePerm).Do(genInterceptPolicy(t, defaultCosDevicePolicyBytes)).Return(nil)
manager := NewManager(mockAPI)
if err := manager.InitDevicePolicy(nil); err != nil {
t.Error(err)
}
}
// TestSetInstanceConfig_NewConfig tests whether the SetInstanceConfig function is able to
// correctly write new device policy files to disk.
func TestSetInstanceConfig_NewConfig(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Create fake policy.
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, instanceConfig)
// The public/private keys should be read from file.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true)
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true)
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil)
// Policy file doesn't exist, so a new file should be created.
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0))
mockAPI.EXPECT().FileExists(policyFile).Return(false)
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, policyBytes)).Return(nil)
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(),
cosDevicePolicyFilePerm).Do(genInterceptPolicy(t, cosDevicePolicyBytes)).Return(nil)
manager := NewManager(mockAPI)
if err := manager.SetInstanceConfig(instanceConfig); err != nil {
t.Error(err)
}
}
// TestSetInstanceConfig_UpdatedConfig tests whether the SetInstanceConfig function is able to
// correctly update the existing device policy file on disk.
func TestSetInstanceConfig_UpdatedConfig(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeyBits)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Create fake policy.
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, instanceConfig)
// The public/private keys should be read from file.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true)
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true)
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil)
// Policy files exist.
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0))
mockAPI.EXPECT().FileExists(policyFile).Return(true)
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true)
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil)
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(cosDevicePolicyBytes, nil)
// This is different than original instanceConfig.
newInstanceConfig := getDefaultInstanceConfigFromBase(
&pb.InstanceConfig{
TargetVersionPrefix: proto.String("8011."),
RebootAfterUpdate: proto.Bool(true),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(false),
},
})
newPolicyBytes, newCosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, newInstanceConfig)
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, newPolicyBytes)).Return(nil)
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, newCosDevicePolicyBytes)).Return(nil)
manager := NewManager(mockAPI)
if err := manager.SetInstanceConfig(newInstanceConfig); err != nil {
t.Error(err)
}
}
// TestSetInstanceConfig_IdenticalConfig tests whether the SetInstanceConfig function skips
// writing the policy files when there is no change in the instance config.
func TestSetInstanceConfig_IdenticalConfig(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeyBits)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Create fake policy.
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, instanceConfig)
// The public/private keys should be read from file.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true)
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true)
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil)
// Policy files exist.
mockAPI.EXPECT().FileExists(policyFile).Return(true)
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true)
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil)
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(cosDevicePolicyBytes, nil)
// AtomicWriteFile not expected since there is no change in the instanceConfig.
manager := NewManager(mockAPI)
if err := manager.SetInstanceConfig(instanceConfig); err != nil {
t.Error(err)
}
}
// TestSetInstanceConfig_EmptyConfig tests whether the SetInstanceConfig function sets
// correct default values when an empty InstanceConfig is passed.
func TestSetInstanceConfig_EmptyConfig(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeyBits)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Create fake policy. Start with default instnace config.
defaultInstanceConfig := getDefaultInstanceConfigFromBase(&pb.InstanceConfig{})
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, defaultInstanceConfig)
// The public/private keys should be read from file.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true)
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true)
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil)
// Policy file exist; simulate its read.
mockAPI.EXPECT().FileExists(policyFile).Return(true)
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true)
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil)
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(cosDevicePolicyBytes, nil)
// This is just an empty instnace config.
emptyInstanceConfig := &pb.InstanceConfig{}
// AtomicWriteFile not expected since empty config should resolve to the already present
// default config.
manager := NewManager(mockAPI)
if err := manager.SetInstanceConfig(emptyInstanceConfig); err != nil {
t.Error(err)
}
}
// TestSetInstanceConfig_NilConfig tests whether the SetInstanceConfig function sets
// correct default values when an nil InstnaceConfig is passed.
func TestSetInstanceConfig_NilConfig(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeyBits)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Create fake policy. Start with default instnace config.
defaultInstanceConfig := getDefaultInstanceConfigFromBase(&pb.InstanceConfig{})
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, defaultInstanceConfig)
// The public/private keys should be read from file.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true)
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true)
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil)
// Policy file exist; simulate its read.
mockAPI.EXPECT().FileExists(policyFile).Return(true)
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true)
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil)
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(cosDevicePolicyBytes, nil)
// AtomicWriteFile not expected since empty config should resolve to the already present
// default config.
manager := NewManager(mockAPI)
if err := manager.SetInstanceConfig(nil); err != nil {
t.Error(err)
}
}
// TestSetInstanceConfig_RemoveConfig tests whether the SetInstanceConfig function sets
// correct default values for a removed config option.
func TestSetInstanceConfig_RemoveConfig(t *testing.T) {
// Create mock private/public keys.
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeyBits)
if err != nil {
t.Fatal(err)
}
publicKey := privateKey.Public().(*rsa.PublicKey)
// Marshal the keys.
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
t.Fatal(err)
}
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAPI := mocksysapi.NewMockAPIHandler(mockCtrl)
// Create fake policy.
policyBytes, cosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, instanceConfig)
// The public/private keys should be read from file.
mockAPI.EXPECT().FileExists(privateKeyFile).Return(true)
mockAPI.EXPECT().FileExists(publicKeyFile).Return(true)
mockAPI.EXPECT().ReadFile(publicKeyFile).Return(publicKeyBytes, nil)
mockAPI.EXPECT().ReadFile(privateKeyFile).Return(privateKeyBytes, nil)
// Policy file exist; simulate its read.
mockAPI.EXPECT().GetUnixTimestamp().Return(int64(0))
mockAPI.EXPECT().FileExists(policyFile).Return(true)
mockAPI.EXPECT().FileExists(cosDevicePolicyFile).Return(true)
mockAPI.EXPECT().ReadFile(policyFile).Return(policyBytes, nil)
mockAPI.EXPECT().ReadFile(cosDevicePolicyFile).Return(cosDevicePolicyBytes, nil)
// Note that UpdateScatterSeconds is not specified.
newInstanceConfig := &pb.InstanceConfig{
MetricsEnabled: proto.Bool(true),
TargetVersionPrefix: proto.String("8099."),
RebootAfterUpdate: proto.Bool(false),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(true),
},
}
// expectedInstanceConfig is same as newInstanceConfig with default value for the
// missing config option.
expectedInstanceConfig := &pb.InstanceConfig{
MetricsEnabled: proto.Bool(true),
TargetVersionPrefix: proto.String("8099."),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(33),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(true),
MonitoringEnabled: proto.Bool(false),
},
}
expectedPolicyBytes, expectedCosDevicePolicyBytes := generateFakePolicyBytes(t, privateKey, expectedInstanceConfig)
mockAPI.EXPECT().AtomicWriteFile(policyFile, gomock.Any(),
policyFilePerm).Do(genInterceptPolicy(t, expectedPolicyBytes)).Return(nil)
mockAPI.EXPECT().AtomicWriteFile(cosDevicePolicyFile, gomock.Any(),
cosDevicePolicyFilePerm).Do(genInterceptPolicy(t, expectedCosDevicePolicyBytes)).Return(nil)
manager := NewManager(mockAPI)
if err := manager.SetInstanceConfig(newInstanceConfig); err != nil {
t.Error(err)
}
}
// TestGetDefaultInstanceConfigFromBase tests that we always generate the expect default
// update config.
func TestGetDefaultInstanceConfigFromBase(t *testing.T) {
// Expected default config.
config := &pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String(""),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(33),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(false),
LoggingEnabled: proto.Bool(false),
MonitoringEnabled: proto.Bool(false),
},
}
tests := []struct {
name string
baseConfig *pb.InstanceConfig
expectedConfig *pb.InstanceConfig
}{
{
"NilBaseConfig",
nil,
config,
},
{
"EmptyBaseConfig",
&pb.InstanceConfig{
HealthMonitorConfig: &pb.HealthMonitorConfig{},
},
config,
},
{
"BaseConfigWithCustomTargetVersionPrefix",
&pb.InstanceConfig{
TargetVersionPrefix: proto.String("1.2.3"),
},
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String("1.2.3"),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(33),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(false),
LoggingEnabled: proto.Bool(false),
MonitoringEnabled: proto.Bool(false),
},
},
},
{
"BaseConfigWithMetricsEnabled",
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(true),
},
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(true),
TargetVersionPrefix: proto.String(""),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(33),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(false),
LoggingEnabled: proto.Bool(false),
MonitoringEnabled: proto.Bool(false),
},
},
},
{
"BaseConfigWithUpdateScatterSeconds",
&pb.InstanceConfig{
UpdateScatterSeconds: proto.Int64(99),
},
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String(""),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(false),
LoggingEnabled: proto.Bool(false),
MonitoringEnabled: proto.Bool(false),
},
},
},
{
"BaseConfigWithMultipleFields",
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(true),
TargetVersionPrefix: proto.String("1.2.3"),
UpdateScatterSeconds: proto.Int64(99),
},
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(true),
TargetVersionPrefix: proto.String("1.2.3"),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(false),
LoggingEnabled: proto.Bool(false),
MonitoringEnabled: proto.Bool(false),
},
},
},
{
"BaseConfigEnablingLoggingMissingMonitoring",
&pb.InstanceConfig{
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(true),
},
},
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String(""),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(true),
MonitoringEnabled: proto.Bool(false),
},
},
},
{
"BaseConfigEnforcedNotEnableLoggingMonitoring",
&pb.InstanceConfig{
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(false),
MonitoringEnabled: proto.Bool(false),
},
},
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String(""),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(false),
MonitoringEnabled: proto.Bool(false),
},
},
},
{
"BaseConfigEnableLoggingAndMonitoring",
&pb.InstanceConfig{
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(true),
MonitoringEnabled: proto.Bool(true),
},
},
&pb.InstanceConfig{
MetricsEnabled: proto.Bool(false),
TargetVersionPrefix: proto.String(""),
RebootAfterUpdate: proto.Bool(false),
UpdateScatterSeconds: proto.Int64(99),
HealthMonitorConfig: &pb.HealthMonitorConfig{
Enforced: proto.Bool(true),
LoggingEnabled: proto.Bool(true),
MonitoringEnabled: proto.Bool(true),
},
},
},
}
for _, test := range tests {
generatedConfig := getDefaultInstanceConfigFromBase(test.baseConfig)
if !proto.Equal(generatedConfig, test.expectedConfig) {
t.Errorf("In test %s, got %s, expected %s",
test.name,
proto.MarshalTextString(generatedConfig),
proto.MarshalTextString(test.expectedConfig))
}
}
}