From 97477b09b3ee5244808e89880c50ba5f26a7921f Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Wed, 23 Sep 2020 12:40:01 -0700 Subject: [PATCH 1/5] Add IMA feature to the kernel, add config for it - Add IMA measurement configs to the x86_64, and aarch64 kernel configs (IMA_APPRAISE currently disabled). - Add KernelCommandLine config field to control IMA, and allow additional configs to be passed. Signed-off-by: Daniel McIlvaney --- SPECS/kernel/config | 29 +++-- SPECS/kernel/config_aarch64 | 33 ++++-- SPECS/kernel/kernel.signatures.json | 4 +- toolkit/docs/formats/imageconfig.md | 21 ++++ toolkit/resources/assets/grub2/grub.cfg | 2 +- .../tools/imagegen/configuration/imapolicy.go | 67 +++++++++++ .../imagegen/configuration/imapolicy_test.go | 78 ++++++++++++ .../configuration/kernelcommandline.go | 61 ++++++++++ .../configuration/kernelcommandline_test.go | 112 ++++++++++++++++++ .../imagegen/configuration/systemconfig.go | 5 + .../configuration/systemconfig_test.go | 15 +++ .../imagegen/installutils/installutils.go | 60 +++++++++- toolkit/tools/imager/imager.go | 2 +- 13 files changed, 468 insertions(+), 21 deletions(-) create mode 100644 toolkit/tools/imagegen/configuration/imapolicy.go create mode 100644 toolkit/tools/imagegen/configuration/imapolicy_test.go create mode 100644 toolkit/tools/imagegen/configuration/kernelcommandline.go create mode 100644 toolkit/tools/imagegen/configuration/kernelcommandline_test.go diff --git a/SPECS/kernel/config b/SPECS/kernel/config index 51efec84edd..76d5dd96b24 100644 --- a/SPECS/kernel/config +++ b/SPECS/kernel/config @@ -2957,7 +2957,7 @@ CONFIG_IPMI_SI=m # CONFIG_IPMI_SSIF is not set CONFIG_IPMI_WATCHDOG=m CONFIG_IPMI_POWEROFF=m -CONFIG_HW_RANDOM=m +CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_TIMERIOMEM=m CONFIG_HW_RANDOM_INTEL=m CONFIG_HW_RANDOM_AMD=m @@ -2972,10 +2972,10 @@ CONFIG_HPET=y CONFIG_HPET_MMAP=y CONFIG_HPET_MMAP_DEFAULT=y CONFIG_HANGCHECK_TIMER=m -CONFIG_TCG_TPM=m +CONFIG_TCG_TPM=y CONFIG_HW_RANDOM_TPM=y -CONFIG_TCG_TIS_CORE=m -CONFIG_TCG_TIS=m +CONFIG_TCG_TIS_CORE=y +CONFIG_TCG_TIS=y CONFIG_TCG_TIS_I2C_ATMEL=m CONFIG_TCG_TIS_I2C_INFINEON=m CONFIG_TCG_TIS_I2C_NUVOTON=m @@ -2983,7 +2983,7 @@ CONFIG_TCG_NSC=m CONFIG_TCG_ATMEL=m CONFIG_TCG_INFINEON=m CONFIG_TCG_XEN=m -CONFIG_TCG_CRB=m +CONFIG_TCG_CRB=y # CONFIG_TCG_VTPM_PROXY is not set # CONFIG_TCG_TIS_ST33ZP24_I2C is not set # CONFIG_TELCLOCK is not set @@ -6027,7 +6027,22 @@ CONFIG_SECURITY_SAFESETID=y CONFIG_INTEGRITY=y # CONFIG_INTEGRITY_SIGNATURE is not set CONFIG_INTEGRITY_AUDIT=y -# CONFIG_IMA is not set +CONFIG_IMA=y +CONFIG_IMA_MEASURE_PCR_IDX=10 +CONFIG_IMA_LSM_RULES=y +# CONFIG_IMA_TEMPLATE is not set +# CONFIG_IMA_NG_TEMPLATE is not set +CONFIG_IMA_SIG_TEMPLATE=y +CONFIG_IMA_DEFAULT_TEMPLATE="ima-sig" +# CONFIG_IMA_DEFAULT_HASH_SHA1 is not set +CONFIG_IMA_DEFAULT_HASH_SHA256=y +# CONFIG_IMA_DEFAULT_HASH_SHA512 is not set +CONFIG_IMA_DEFAULT_HASH="sha256" +CONFIG_IMA_WRITE_POLICY=y +CONFIG_IMA_READ_POLICY=y +# CONFIG_IMA_APPRAISE is not set +CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS=y +CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS=y # CONFIG_EVM is not set # CONFIG_DEFAULT_SECURITY_SELINUX is not set # CONFIG_DEFAULT_SECURITY_SMACK is not set @@ -6144,7 +6159,7 @@ CONFIG_CRYPTO_ESSIV=m # Hash modes # CONFIG_CRYPTO_CMAC=m -CONFIG_CRYPTO_HMAC=m +CONFIG_CRYPTO_HMAC=y # CONFIG_CRYPTO_XCBC is not set # CONFIG_CRYPTO_VMAC is not set diff --git a/SPECS/kernel/config_aarch64 b/SPECS/kernel/config_aarch64 index eefbd298a30..402984d4b68 100644 --- a/SPECS/kernel/config_aarch64 +++ b/SPECS/kernel/config_aarch64 @@ -2879,7 +2879,7 @@ CONFIG_IPMI_SI=m CONFIG_IPMI_WATCHDOG=m CONFIG_IPMI_POWEROFF=m # CONFIG_IPMB_DEVICE_INTERFACE is not set -CONFIG_HW_RANDOM=m +CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_TIMERIOMEM=m CONFIG_HW_RANDOM_BCM2835=m CONFIG_HW_RANDOM_IPROC_RNG200=m @@ -2894,10 +2894,10 @@ CONFIG_HW_RANDOM_CAVIUM=m # CONFIG_APPLICOM is not set CONFIG_RAW_DRIVER=m CONFIG_MAX_RAW_DEVS=8192 -CONFIG_TCG_TPM=m +CONFIG_TCG_TPM=y CONFIG_HW_RANDOM_TPM=y -CONFIG_TCG_TIS_CORE=m -CONFIG_TCG_TIS=m +CONFIG_TCG_TIS_CORE=y +CONFIG_TCG_TIS=y CONFIG_TCG_TIS_SPI=m CONFIG_TCG_TIS_I2C_ATMEL=m CONFIG_TCG_TIS_I2C_INFINEON=m @@ -2905,7 +2905,7 @@ CONFIG_TCG_TIS_I2C_NUVOTON=m CONFIG_TCG_ATMEL=m CONFIG_TCG_INFINEON=m CONFIG_TCG_XEN=m -# CONFIG_TCG_CRB is not set +CONFIG_TCG_CRB=y # CONFIG_TCG_VTPM_PROXY is not set # CONFIG_TCG_TIS_ST33ZP24_I2C is not set # CONFIG_TCG_TIS_ST33ZP24_SPI is not set @@ -6290,7 +6290,22 @@ CONFIG_SECURITY_SAFESETID=y CONFIG_INTEGRITY=y # CONFIG_INTEGRITY_SIGNATURE is not set CONFIG_INTEGRITY_AUDIT=y -# CONFIG_IMA is not set +CONFIG_IMA=y +CONFIG_IMA_MEASURE_PCR_IDX=10 +CONFIG_IMA_LSM_RULES=y +# CONFIG_IMA_TEMPLATE is not set +# CONFIG_IMA_NG_TEMPLATE is not set +CONFIG_IMA_SIG_TEMPLATE=y +CONFIG_IMA_DEFAULT_TEMPLATE="ima-sig" +# CONFIG_IMA_DEFAULT_HASH_SHA1 is not set +CONFIG_IMA_DEFAULT_HASH_SHA256=y +# CONFIG_IMA_DEFAULT_HASH_SHA512 is not set +CONFIG_IMA_DEFAULT_HASH="sha256" +CONFIG_IMA_WRITE_POLICY=y +CONFIG_IMA_READ_POLICY=y +# CONFIG_IMA_APPRAISE is not set +CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS=y +CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS=y # CONFIG_EVM is not set # CONFIG_DEFAULT_SECURITY_SELINUX is not set # CONFIG_DEFAULT_SECURITY_SMACK is not set @@ -6399,7 +6414,7 @@ CONFIG_CRYPTO_ESSIV=m # Hash modes # CONFIG_CRYPTO_CMAC=m -CONFIG_CRYPTO_HMAC=m +CONFIG_CRYPTO_HMAC=y # CONFIG_CRYPTO_XCBC is not set # CONFIG_CRYPTO_VMAC is not set @@ -6420,8 +6435,8 @@ CONFIG_CRYPTO_MD5=y # CONFIG_CRYPTO_RMD256 is not set # CONFIG_CRYPTO_RMD320 is not set CONFIG_CRYPTO_SHA1=y -CONFIG_CRYPTO_LIB_SHA256=m -CONFIG_CRYPTO_SHA256=m +CONFIG_CRYPTO_LIB_SHA256=y +CONFIG_CRYPTO_SHA256=y CONFIG_CRYPTO_SHA512=y # CONFIG_CRYPTO_SHA3 is not set # CONFIG_CRYPTO_SM3 is not set diff --git a/SPECS/kernel/kernel.signatures.json b/SPECS/kernel/kernel.signatures.json index 27d2b2b9ee1..272fb40021d 100644 --- a/SPECS/kernel/kernel.signatures.json +++ b/SPECS/kernel/kernel.signatures.json @@ -1,7 +1,7 @@ { "Signatures": { - "config": "cb99faaac82f05b84539e4b99633b5a444de5b2db01ed37946afa0360d1f94f0", - "config_aarch64": "98bcf0f9c9fa02e11ad255ae352461b8ef7d53daf02c707a8a9b53f9bfb32db3", + "config": "ac1f71bde2b05e417e5d4fe4a72ffa5b0376ac53136e61e7e080e4f2a97ef31c", + "config_aarch64": "5405f228f0da37ad2460047c83930f89d76f7697fd3835848ef2092587d78104", "linux-msft-5.4.51.tar.gz": "3bcd6b09e952fac4f708614658b508ce80c8e25c04780b6b44a481b1479a08e7" } } \ No newline at end of file diff --git a/toolkit/docs/formats/imageconfig.md b/toolkit/docs/formats/imageconfig.md index 73ace0cd0a4..325b4f194b6 100644 --- a/toolkit/docs/formats/imageconfig.md +++ b/toolkit/docs/formats/imageconfig.md @@ -141,6 +141,23 @@ A sample KernelOptions specifying a default kernel and a specialized kernel for }, ``` +### KernelCommandLine + +KernelCommandLine is an optional key which allows additional parameters to be passed to the kernel when it is launched from Grub. + +ImaPolicy is a list of Integrity Measurement Architecture (IMA) policies to enable, they may be any combination of `tcb`, `appraise_tcb`, `secure_boot`. + +ExtraCommandLine is a string which will be appended to the end of the kernel command line and may contain any additional parameters desired. The `` ` `` character is reserved and may not be used. + +A sample KernelCommandLine enabling a basic IMA mode and passing two additional parameters: + +``` json +"KernelCommandLine": { + "ImaPolicy": ["tcb"], + "ExtraCommandLine": "my_first_param=foo my_second_param=\"bar baz\"" +}, +``` + # Sample image configuration A sample image configuration, producing a VHDX disk image: @@ -200,6 +217,10 @@ A sample image configuration, producing a VHDX disk image: "KernelOptions": { "default": "kernel" }, + "KernelCommandLine": { + "ImaPolicy": ["tcb"], + "ExtraCommandLine": "my_first_param=foo my_second_param=\"bar baz\"" + }, "Hostname": "cbl-mariner" } ] diff --git a/toolkit/resources/assets/grub2/grub.cfg b/toolkit/resources/assets/grub2/grub.cfg index 19407c001a4..4a39bf25aae 100644 --- a/toolkit/resources/assets/grub2/grub.cfg +++ b/toolkit/resources/assets/grub2/grub.cfg @@ -11,7 +11,7 @@ fi set rootdevice={{.RootPartition}} menuentry "CBL-Mariner" { - linux /boot/$mariner_linux {{.LuksUUID}} {{.LVM}} rd.auto=1 root=$rootdevice $mariner_cmdline $systemd_cmdline + linux /boot/$mariner_linux {{.LuksUUID}} {{.LVM}} {{.IMAPolicy}} rd.auto=1 root=$rootdevice $mariner_cmdline $systemd_cmdline {{.ExtraCommandLine}} if [ -f /boot/$mariner_initrd ]; then initrd /boot/$mariner_initrd fi diff --git a/toolkit/tools/imagegen/configuration/imapolicy.go b/toolkit/tools/imagegen/configuration/imapolicy.go new file mode 100644 index 00000000000..da4d072700d --- /dev/null +++ b/toolkit/tools/imagegen/configuration/imapolicy.go @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Parser for the image builder's configuration schemas. + +package configuration + +import ( + "encoding/json" + "fmt" +) + +// ImaPolicy sets the ima_policy kernel command line option +type ImaPolicy string + +const ( + // ImaPolicyTcb selects the tcb IMA policy + ImaPolicyTcb ImaPolicy = "tcb" + // ImaPolicyAppraiseTcb selects the appraise_tcb IMA policy + ImaPolicyAppraiseTcb ImaPolicy = "appraise_tcb" + // ImaPolicySecureBoot selects the secure_boot IMA policy + ImaPolicySecureBoot ImaPolicy = "secure_boot" + // ImaPolicyNone selects no IMA policy + ImaPolicyNone ImaPolicy = "" +) + +func (i ImaPolicy) String() string { + return fmt.Sprint(string(i)) +} + +// GetValidImaPolicies returns a list of all the supported +// disk partition types +func (i *ImaPolicy) GetValidImaPolicies() (types []ImaPolicy) { + return []ImaPolicy{ + ImaPolicyTcb, + ImaPolicyAppraiseTcb, + ImaPolicySecureBoot, + ImaPolicyNone, + } +} + +// IsValid returns an error if the ImaPolicy is not valid +func (i *ImaPolicy) IsValid() (err error) { + for _, valid := range i.GetValidImaPolicies() { + if *i == valid { + return + } + } + return fmt.Errorf("invalid value for ImaPolicy (%s)", i) +} + +// UnmarshalJSON Unmarshals an ImaPolicy entry +func (i *ImaPolicy) UnmarshalJSON(b []byte) (err error) { + // Use an intermediate type which will use the default JSON unmarshal implementation + type IntermediateTypeImaPolicy ImaPolicy + err = json.Unmarshal(b, (*IntermediateTypeImaPolicy)(i)) + if err != nil { + return fmt.Errorf("failed to parse [ImaPolicy]: %w", err) + } + + // Now validate the resulting unmarshaled object + err = i.IsValid() + if err != nil { + return fmt.Errorf("failed to parse [ImaPolicy]: %w", err) + } + return +} diff --git a/toolkit/tools/imagegen/configuration/imapolicy_test.go b/toolkit/tools/imagegen/configuration/imapolicy_test.go new file mode 100644 index 00000000000..2e569b61b1d --- /dev/null +++ b/toolkit/tools/imagegen/configuration/imapolicy_test.go @@ -0,0 +1,78 @@ +// Copyright Microsoft Corporation. +// Licensed under the MIT License. + +package configuration + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestMain found in configuration_test.go. + +var ( + validImaPolicies = []ImaPolicy{ + ImaPolicy("tcb"), + ImaPolicy("appraise_tcb"), + ImaPolicy("secure_boot"), + ImaPolicy(""), + } + invalidImaPolicy = ImaPolicy("not_a_policy") + validImaJSON = `"tcb"` + invalidImaJSON = `1234` +) + +func TestShouldSucceedValidImaPoliciesMatch_ImaPolicy(t *testing.T) { + var ima ImaPolicy + assert.Equal(t, len(validImaPolicies), len(ima.GetValidImaPolicies())) + + for _, imaPolicy := range validImaPolicies { + found := false + for _, validImaPolicy := range ima.GetValidImaPolicies() { + if imaPolicy == validImaPolicy { + found = true + } + } + assert.True(t, found) + } +} + +func TestShouldSucceedParsingValidPolicies_ImaPolicy(t *testing.T) { + for _, validPolicy := range validImaPolicies { + var checkedPolicy ImaPolicy + + assert.NoError(t, validPolicy.IsValid()) + err := remarshalJSON(validPolicy, &checkedPolicy) + assert.NoError(t, err) + assert.Equal(t, validPolicy, checkedPolicy) + } +} + +func TestShouldFailParsingInvalidPolicy_ImaPolicy(t *testing.T) { + var checkedPolicy ImaPolicy + + err := invalidImaPolicy.IsValid() + assert.Error(t, err) + assert.Equal(t, "invalid value for ImaPolicy (not_a_policy)", err.Error()) + + err = remarshalJSON(invalidImaPolicy, &checkedPolicy) + assert.Error(t, err) + assert.Equal(t, "failed to parse [ImaPolicy]: invalid value for ImaPolicy (not_a_policy)", err.Error()) +} + +func TestShouldSucceedParsingValidJSON_ImaPolicy(t *testing.T) { + var checkedPolicy ImaPolicy + + err := marshalJSONString(validImaJSON, &checkedPolicy) + assert.NoError(t, err) + assert.Equal(t, validImaPolicies[0], checkedPolicy) +} + +func TestShouldFailParsingInvalidJSON_ImaPolicy(t *testing.T) { + var checkedPolicy ImaPolicy + + err := marshalJSONString(invalidImaJSON, &checkedPolicy) + assert.Error(t, err) + assert.Equal(t, "failed to parse [ImaPolicy]: json: cannot unmarshal number into Go value of type configuration.IntermediateTypeImaPolicy", err.Error()) +} diff --git a/toolkit/tools/imagegen/configuration/kernelcommandline.go b/toolkit/tools/imagegen/configuration/kernelcommandline.go new file mode 100644 index 00000000000..e3462542931 --- /dev/null +++ b/toolkit/tools/imagegen/configuration/kernelcommandline.go @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Parser for the image builder's configuration schemas. + +package configuration + +import ( + "encoding/json" + "fmt" + "strings" +) + +// KernelCommandLine holds extra command line parameters which can be +// added to the grub config file. +// - ImaPolicy: A list of IMA policies which will be used together +// - ExtraCommandLine: Arbitrary parameters which will be appended to the +// end of the kernel command line +type KernelCommandLine struct { + ImaPolicy []ImaPolicy `json:"ImaPolicy"` + ExtraCommandLine string `json:"ExtraCommandLine"` +} + +// GetSedDelimeter returns the delimeter which should be used with sed +// to find/replace the command line strings. +func (k *KernelCommandLine) GetSedDelimeter() (delimeter string) { + return "`" +} + +// IsValid returns an error if the KernelCommandLine is not valid +func (k *KernelCommandLine) IsValid() (err error) { + for _, ima := range k.ImaPolicy { + if err = ima.IsValid(); err != nil { + return + } + } + + // A character needs to be set aside for use as the sed delimiter, make sure it isn't included in the provided string + if strings.Contains(k.ExtraCommandLine, k.GetSedDelimeter()) { + return fmt.Errorf("ExtraCommandLine contains character %s which is reserved for use by sed", k.GetSedDelimeter()) + } + + return +} + +// UnmarshalJSON Unmarshals a KernelCommandLine entry +func (k *KernelCommandLine) UnmarshalJSON(b []byte) (err error) { + // Use an intermediate type which will use the default JSON unmarshal implementation + type IntermediateTypeKernelCommandLine KernelCommandLine + err = json.Unmarshal(b, (*IntermediateTypeKernelCommandLine)(k)) + if err != nil { + return fmt.Errorf("failed to parse [KernelCommandLine]: %w", err) + } + + // Now validate the resulting unmarshaled object + err = k.IsValid() + if err != nil { + return fmt.Errorf("failed to parse [KernelCommandLine]: %w", err) + } + return +} diff --git a/toolkit/tools/imagegen/configuration/kernelcommandline_test.go b/toolkit/tools/imagegen/configuration/kernelcommandline_test.go new file mode 100644 index 00000000000..e6d85dc5964 --- /dev/null +++ b/toolkit/tools/imagegen/configuration/kernelcommandline_test.go @@ -0,0 +1,112 @@ +// Copyright Microsoft Corporation. +// Licensed under the MIT License. + +package configuration + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +//TestMain found in configuration_test.go. + +var ( + validCommandLine KernelCommandLine = KernelCommandLine{ + ImaPolicy: []ImaPolicy{ + ImaPolicyTcb, + }, + ExtraCommandLine: "param1=value param2=\"value2 value3\"", + } + invalidExtraCommandLine = "invalid=`delim`" + validExtraComandLineJSON = `{"ImaPolicy": ["tcb"], "ExtraCommandLine": "param1=value param2=\"value2 value3\""}` + invalidExtraComandLineJSON1 = `{"ImaPolicy": [ "not-an-ima-policy" ]}` + invalidExtraComandLineJSON2 = `{"ExtraCommandLine": "` + invalidExtraCommandLine + `"}` +) + +func TestShouldSucceedParsingDefaultCommandLine_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + err := marshalJSONString("{}", &checkedCommandline) + assert.NoError(t, err) + assert.Equal(t, KernelCommandLine{}, checkedCommandline) +} + +func TestShouldSucceedParseValidCommandLine_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + + assert.NoError(t, validCommandLine.IsValid()) + err := remarshalJSON(validCommandLine, &checkedCommandline) + assert.NoError(t, err) + assert.Equal(t, validCommandLine, checkedCommandline) +} + +func TestShouldSucceedParsingMultipleIma_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + multipleImaCommandLine := validCommandLine + multipleImaCommandLine.ImaPolicy = append(multipleImaCommandLine.ImaPolicy, ImaPolicyAppraiseTcb) + + assert.NoError(t, multipleImaCommandLine.IsValid()) + err := remarshalJSON(multipleImaCommandLine, &checkedCommandline) + assert.NoError(t, err) + assert.Equal(t, multipleImaCommandLine, checkedCommandline) +} + +func TestShouldSucceedParsesNoIma_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + nilImaCommandLine := validCommandLine + nilImaCommandLine.ImaPolicy = nil + + assert.NoError(t, nilImaCommandLine.IsValid()) + err := remarshalJSON(nilImaCommandLine, &checkedCommandline) + assert.NoError(t, err) + assert.Equal(t, nilImaCommandLine, checkedCommandline) +} + +func TestShouldFailParsingMixedValidInvalidIma_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + multipleImaCommandLine := validCommandLine + multipleImaCommandLine.ImaPolicy = append(multipleImaCommandLine.ImaPolicy, invalidImaPolicy) + + err := multipleImaCommandLine.IsValid() + assert.Error(t, err) + assert.Equal(t, "invalid value for ImaPolicy (not_a_policy)", err.Error()) + + err = remarshalJSON(multipleImaCommandLine, &checkedCommandline) + assert.Error(t, err) + assert.Equal(t, "failed to parse [KernelCommandLine]: failed to parse [ImaPolicy]: invalid value for ImaPolicy (not_a_policy)", err.Error()) +} + +func TestShouldFailWrongSedDelimeter_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + invalidSedExtraCommandLine := validCommandLine + invalidSedExtraCommandLine.ExtraCommandLine = invalidExtraCommandLine + + err := invalidSedExtraCommandLine.IsValid() + assert.Error(t, err) + assert.Equal(t, "ExtraCommandLine contains character ` which is reserved for use by sed", err.Error()) + + err = remarshalJSON(invalidSedExtraCommandLine, &checkedCommandline) + assert.Error(t, err) + assert.Equal(t, "failed to parse [KernelCommandLine]: ExtraCommandLine contains character ` which is reserved for use by sed", err.Error()) +} + +func TestShouldSucceedParsingValidJSON_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + + err := marshalJSONString(validExtraComandLineJSON, &checkedCommandline) + assert.NoError(t, err) + assert.Equal(t, validCommandLine, checkedCommandline) +} + +func TestShouldFailParsingInvalidJSON_KernelCommandLine(t *testing.T) { + var checkedCommandline KernelCommandLine + + err := marshalJSONString(invalidExtraComandLineJSON1, &checkedCommandline) + assert.Error(t, err) + assert.Equal(t, "failed to parse [KernelCommandLine]: failed to parse [ImaPolicy]: invalid value for ImaPolicy (not-an-ima-policy)", err.Error()) + + checkedCommandline = KernelCommandLine{} + err = marshalJSONString(invalidExtraComandLineJSON2, &checkedCommandline) + assert.Error(t, err) + assert.Equal(t, "failed to parse [KernelCommandLine]: ExtraCommandLine contains character ` which is reserved for use by sed", err.Error()) +} diff --git a/toolkit/tools/imagegen/configuration/systemconfig.go b/toolkit/tools/imagegen/configuration/systemconfig.go index 8e459269769..c31476fd4e1 100644 --- a/toolkit/tools/imagegen/configuration/systemconfig.go +++ b/toolkit/tools/imagegen/configuration/systemconfig.go @@ -19,6 +19,7 @@ type SystemConfig struct { Name string `json:"Name"` PackageLists []string `json:"PackageLists"` KernelOptions map[string]string `json:"KernelOptions"` + KernelCommandLine KernelCommandLine `json:"KernelCommandLine"` AdditionalFiles map[string]string `json:"AdditionalFiles"` PartitionSettings []PartitionSetting `json:"PartitionSettings"` PostInstallScripts []PostInstallScript `json:"PostInstallScripts"` @@ -67,6 +68,10 @@ func (s *SystemConfig) IsValid() (err error) { // } } + if err = s.KernelCommandLine.IsValid(); err != nil { + return fmt.Errorf("invalid [KernelCommandLine]: %w", err) + } + //Validate PartitionSettings //Validate PostInstallScripts //Validate Groups diff --git a/toolkit/tools/imagegen/configuration/systemconfig_test.go b/toolkit/tools/imagegen/configuration/systemconfig_test.go index f7d415820b4..183c8b9e1bf 100644 --- a/toolkit/tools/imagegen/configuration/systemconfig_test.go +++ b/toolkit/tools/imagegen/configuration/systemconfig_test.go @@ -106,6 +106,21 @@ func TestShouldSucceedParsingMissingDefaultKernelForRootfs_SystemConfig(t *testi assert.Equal(t, rootfsNoKernelConfig, checkedSystemConfig) } +func TestShouldFailParsingBadKernelCommandLine_SystemConfig(t *testing.T) { + var checkedSystemConfig SystemConfig + + badKernelCommandConfig := validSystemConfig + badKernelCommandConfig.KernelCommandLine = KernelCommandLine{ExtraCommandLine: invalidExtraCommandLine} + + err := badKernelCommandConfig.IsValid() + assert.Error(t, err) + assert.Equal(t, "invalid [KernelCommandLine]: ExtraCommandLine contains character ` which is reserved for use by sed", err.Error()) + + err = remarshalJSON(badKernelCommandConfig, &checkedSystemConfig) + assert.Error(t, err) + assert.Equal(t, "failed to parse [SystemConfig]: failed to parse [KernelCommandLine]: ExtraCommandLine contains character ` which is reserved for use by sed", err.Error()) +} + func TestShouldFailToParseInvalidJSON_SystemConfig(t *testing.T) { var checkedSystemConfig SystemConfig diff --git a/toolkit/tools/imagegen/installutils/installutils.go b/toolkit/tools/imagegen/installutils/installutils.go index eac6af18b67..f6ba3f6a4ec 100644 --- a/toolkit/tools/imagegen/installutils/installutils.go +++ b/toolkit/tools/imagegen/installutils/installutils.go @@ -686,9 +686,10 @@ func addEntryToCrypttab(installRoot string, devicePath string, encryptedRoot dis // - rootDevice holds the root partition // - bootUUID is the UUID for the boot partition // - encryptedRoot holds the encrypted root information if encrypted root is enabled +// - kernelCommandLine contains additional kernel parameters which may be optionally set // Note: this boot partition could be different than the boot partition specified in the bootloader. // This boot partition specifically indicates where to find the kernel, config files, and initrd -func InstallGrubCfg(installRoot, rootDevice, bootUUID string, encryptedRoot diskutils.EncryptedRootDevice) (err error) { +func InstallGrubCfg(installRoot, rootDevice, bootUUID string, encryptedRoot diskutils.EncryptedRootDevice, kernelCommandLine configuration.KernelCommandLine) (err error) { const ( assetGrubcfgFile = "/installer/grub2/grub.cfg" grubCfgFile = "boot/grub2/grub.cfg" @@ -729,6 +730,29 @@ func InstallGrubCfg(installRoot, rootDevice, bootUUID string, encryptedRoot disk return } + // Configure IMA policy + err = setGrubCfgIMA(installGrubCfgFile, kernelCommandLine) + if err != nil { + logger.Log.Warnf("Failed to set ima_policy in grub.cfg: %v", err) + return + } + + // Append any additional command line parameters + err = setGrubCfgAdditional(installGrubCfgFile, kernelCommandLine) + if err != nil { + logger.Log.Warnf("Failed to append extra command line parameterse in grub.cfg: %v", err) + return + } + + logger.Log.Errorf("Grub at %s", installGrubCfgFile) + stdout, stderr, err := shell.Execute("ls", filepath.Join(installRoot, "boot/grub2/")) + logger.Log.Error(stdout) + logger.Log.Error(stderr) + + stdout, stderr, err = shell.Execute("cat", installGrubCfgFile) + logger.Log.Error(stdout) + logger.Log.Error(stderr) + return } @@ -1286,6 +1310,40 @@ func runPostInstallScripts(installChroot *safechroot.Chroot, config configuratio return } +func setGrubCfgAdditional(grubPath string, kernelCommandline configuration.KernelCommandLine) (err error) { + const ( + extraPattern = "{{.ExtraCommandLine}}" + sedDelimiter = "@" + ) + + err = sed(extraPattern, kernelCommandline.ExtraCommandLine, kernelCommandline.GetSedDelimeter(), grubPath) + if err != nil { + logger.Log.Warnf("Failed to append extra paramters to grub.cfg: %v", err) + } + + return +} + +func setGrubCfgIMA(grubPath string, kernelCommandline configuration.KernelCommandLine) (err error) { + const ( + imaPrefix = "ima_policy=" + imaPattern = "{{.IMAPolicy}}" + ) + + var ima string + + for _, policy := range kernelCommandline.ImaPolicy { + ima += fmt.Sprintf("%v%v ", imaPrefix, policy) + } + + err = sed(imaPattern, ima, kernelCommandline.GetSedDelimeter(), grubPath) + if err != nil { + logger.Log.Warnf("Failed to set grub.cfg's IMA setting: %v", err) + } + + return +} + func setGrubCfgLVM(grubPath, luksUUID string) (err error) { const ( lvmPrefix = "rd.lvm.lv=" diff --git a/toolkit/tools/imager/imager.go b/toolkit/tools/imager/imager.go index 631214fa8e5..70e672b089d 100644 --- a/toolkit/tools/imager/imager.go +++ b/toolkit/tools/imager/imager.go @@ -503,7 +503,7 @@ func configureDiskBootloader(systemConfig configuration.SystemConfig, installChr rootDevice = fmt.Sprintf("PARTUUID=%v", partUUID) } - err = installutils.InstallGrubCfg(installChroot.RootDir(), rootDevice, bootUUID, encryptedRoot) + err = installutils.InstallGrubCfg(installChroot.RootDir(), rootDevice, bootUUID, encryptedRoot, systemConfig.KernelCommandLine) if err != nil { err = fmt.Errorf("failed to install main grub config file: %s", err) return From 418efc248e7edf825560bcd4f745fa7cae03ea06 Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Wed, 23 Sep 2020 14:24:53 -0700 Subject: [PATCH 2/5] Address feedback --- .../kernel-signed-aarch64.spec | 4 +- .../kernel-signed-x64/kernel-signed-x64.spec | 4 +- SPECS/kernel/kernel.spec | 4 +- .../imagegen/installutils/installutils.go | 38 ++++++++----------- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/SPECS-SIGNED/kernel-signed-aarch64/kernel-signed-aarch64.spec b/SPECS-SIGNED/kernel-signed-aarch64/kernel-signed-aarch64.spec index 4f080461af8..b73052acc24 100644 --- a/SPECS-SIGNED/kernel-signed-aarch64/kernel-signed-aarch64.spec +++ b/SPECS-SIGNED/kernel-signed-aarch64/kernel-signed-aarch64.spec @@ -2,7 +2,7 @@ Summary: Signed Linux Kernel for aarch64 systems Name: kernel-signed-aarch64 Version: 5.4.51 -Release: 5%{?dist} +Release: 6%{?dist} License: GPLv2 URL: https://github.com/microsoft/WSL2-Linux-Kernel Group: System Environment/Kernel @@ -84,6 +84,8 @@ ln -sf linux-%{uname_r}.cfg /boot/mariner.cfg %config %{_localstatedir}/lib/initramfs/kernel/%{uname_r} %changelog +* Wed Sep 23 2020 Daniel McIlvaney 5.4.51-6 +- Update release number * Thu Sep 03 2020 Daniel McIlvaney 5.4.51-5 - Update release number * Thu Sep 03 2020 Chris Co 5.4.51-4 diff --git a/SPECS-SIGNED/kernel-signed-x64/kernel-signed-x64.spec b/SPECS-SIGNED/kernel-signed-x64/kernel-signed-x64.spec index baf4943e668..9a4c3a2ec67 100644 --- a/SPECS-SIGNED/kernel-signed-x64/kernel-signed-x64.spec +++ b/SPECS-SIGNED/kernel-signed-x64/kernel-signed-x64.spec @@ -2,7 +2,7 @@ Summary: Signed Linux Kernel for x86_64 systems Name: kernel-signed-x64 Version: 5.4.51 -Release: 5%{?dist} +Release: 6%{?dist} License: GPLv2 URL: https://github.com/microsoft/WSL2-Linux-Kernel Group: System Environment/Kernel @@ -84,6 +84,8 @@ ln -sf linux-%{uname_r}.cfg /boot/mariner.cfg %config %{_localstatedir}/lib/initramfs/kernel/%{uname_r} %changelog +* Wed Sep 23 2020 Daniel McIlvaney 5.4.51-6 +- Update release number * Thu Sep 03 2020 Daniel McIlvaney 5.4.51-5 - Update release number * Thu Sep 03 2020 Chris Co 5.4.51-4 diff --git a/SPECS/kernel/kernel.spec b/SPECS/kernel/kernel.spec index 5d4c39e4055..669f6048fc5 100644 --- a/SPECS/kernel/kernel.spec +++ b/SPECS/kernel/kernel.spec @@ -2,7 +2,7 @@ Summary: Linux Kernel Name: kernel Version: 5.4.51 -Release: 5%{?dist} +Release: 6%{?dist} License: GPLv2 URL: https://github.com/microsoft/WSL2-Linux-Kernel Group: System Environment/Kernel @@ -332,6 +332,8 @@ ln -sf linux-%{uname_r}.cfg /boot/mariner.cfg %{_libdir}/perf/include/bpf/* %changelog +* Wed Sep 23 2020 Daniel McIlvaney 5.4.51-6 +- Enable CONFIG_IMA (measurement only) and associated configs * Thu Sep 03 2020 Daniel McIlvaney 5.4.51-5 - Add code to check for missing config flags in the checked in configs * Thu Sep 03 2020 Chris Co 5.4.51-4 diff --git a/toolkit/tools/imagegen/installutils/installutils.go b/toolkit/tools/imagegen/installutils/installutils.go index f6ba3f6a4ec..5dc795442df 100644 --- a/toolkit/tools/imagegen/installutils/installutils.go +++ b/toolkit/tools/imagegen/installutils/installutils.go @@ -744,15 +744,6 @@ func InstallGrubCfg(installRoot, rootDevice, bootUUID string, encryptedRoot disk return } - logger.Log.Errorf("Grub at %s", installGrubCfgFile) - stdout, stderr, err := shell.Execute("ls", filepath.Join(installRoot, "boot/grub2/")) - logger.Log.Error(stdout) - logger.Log.Error(stderr) - - stdout, stderr, err = shell.Execute("cat", installGrubCfgFile) - logger.Log.Error(stdout) - logger.Log.Error(stderr) - return } @@ -1313,7 +1304,6 @@ func runPostInstallScripts(installChroot *safechroot.Chroot, config configuratio func setGrubCfgAdditional(grubPath string, kernelCommandline configuration.KernelCommandLine) (err error) { const ( extraPattern = "{{.ExtraCommandLine}}" - sedDelimiter = "@" ) err = sed(extraPattern, kernelCommandline.ExtraCommandLine, kernelCommandline.GetSedDelimeter(), grubPath) @@ -1346,17 +1336,17 @@ func setGrubCfgIMA(grubPath string, kernelCommandline configuration.KernelComman func setGrubCfgLVM(grubPath, luksUUID string) (err error) { const ( - lvmPrefix = "rd.lvm.lv=" - lvmPattern = "{{.LVM}}" - sedDelimiter = "@" + lvmPrefix = "rd.lvm.lv=" + lvmPattern = "{{.LVM}}" ) + var cmdline configuration.KernelCommandLine var lvm string if luksUUID != "" { lvm = fmt.Sprintf("%v%v", lvmPrefix, diskutils.GetEncryptedRootVolPath()) } - err = sed(lvmPattern, lvm, sedDelimiter, grubPath) + err = sed(lvmPattern, lvm, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's LVM setting: %v", err) } @@ -1368,14 +1358,16 @@ func setGrubCfgLuksUUID(grubPath, uuid string) (err error) { const ( luksUUIDPrefix = "luks.uuid=" luksUUIDPattern = "{{.LuksUUID}}" - sedDelimiter = "/" ) - var luksUUID string + var ( + cmdline configuration.KernelCommandLine + luksUUID string + ) if uuid != "" { luksUUID = fmt.Sprintf("%v%v", luksUUIDPrefix, uuid) } - err = sed(luksUUIDPattern, luksUUID, sedDelimiter, grubPath) + err = sed(luksUUIDPattern, luksUUID, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's luksUUID: %v", err) return @@ -1387,10 +1379,10 @@ func setGrubCfgLuksUUID(grubPath, uuid string) (err error) { func setGrubCfgBootUUID(bootUUID, grubPath string) (err error) { const ( bootUUIDPattern = "{{.BootUUID}}" - sedDelimiter = "/" ) + var cmdline configuration.KernelCommandLine - err = sed(bootUUIDPattern, bootUUID, sedDelimiter, grubPath) + err = sed(bootUUIDPattern, bootUUID, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's bootUUID: %v", err) return @@ -1401,12 +1393,12 @@ func setGrubCfgBootUUID(bootUUID, grubPath string) (err error) { func setGrubCfgEncryptedVolume(grubPath string) (err error) { const ( encryptedVolPattern = "{{.EncryptedVolume}}" - sedDelimiter = "@" lvmPrefix = "lvm/" ) + var cmdline configuration.KernelCommandLine encryptedVol := fmt.Sprintf("%v%v%v%v", "(", lvmPrefix, diskutils.GetEncryptedRootVol(), ")") - err = sed(encryptedVolPattern, encryptedVol, sedDelimiter, grubPath) + err = sed(encryptedVolPattern, encryptedVol, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to grub.cfg's encryptedVolume: %v", err) return @@ -1417,14 +1409,14 @@ func setGrubCfgEncryptedVolume(grubPath string) (err error) { func setGrubCfgRootDevice(rootDevice, grubPath, luksUUID string) (err error) { const ( rootDevicePattern = "{{.RootPartition}}" - sedDelimiter = "@" ) + var cmdline configuration.KernelCommandLine if luksUUID != "" { rootDevice = diskutils.GetEncryptedRootVolMapping() } - err = sed(rootDevicePattern, rootDevice, sedDelimiter, grubPath) + err = sed(rootDevicePattern, rootDevice, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's rootDevice: %v", err) return From e7e7faccb0f3be9300ec53984815a879aa7a6dcc Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Wed, 23 Sep 2020 15:16:58 -0700 Subject: [PATCH 3/5] Add some debug prints to the grub config edits --- toolkit/tools/imagegen/installutils/installutils.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/toolkit/tools/imagegen/installutils/installutils.go b/toolkit/tools/imagegen/installutils/installutils.go index 5dc795442df..e2119b18a5b 100644 --- a/toolkit/tools/imagegen/installutils/installutils.go +++ b/toolkit/tools/imagegen/installutils/installutils.go @@ -1306,6 +1306,7 @@ func setGrubCfgAdditional(grubPath string, kernelCommandline configuration.Kerne extraPattern = "{{.ExtraCommandLine}}" ) + logger.Log.Debugf("Adding ExtraCommandLine('%s') to %s", kernelCommandline.ExtraCommandLine, grubPath) err = sed(extraPattern, kernelCommandline.ExtraCommandLine, kernelCommandline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to append extra paramters to grub.cfg: %v", err) @@ -1326,6 +1327,7 @@ func setGrubCfgIMA(grubPath string, kernelCommandline configuration.KernelComman ima += fmt.Sprintf("%v%v ", imaPrefix, policy) } + logger.Log.Debugf("Adding ImaPolicy('%s') to %s", ima, grubPath) err = sed(imaPattern, ima, kernelCommandline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's IMA setting: %v", err) @@ -1346,6 +1348,7 @@ func setGrubCfgLVM(grubPath, luksUUID string) (err error) { lvm = fmt.Sprintf("%v%v", lvmPrefix, diskutils.GetEncryptedRootVolPath()) } + logger.Log.Debugf("Adding lvm('%s') to %s", lvm, grubPath) err = sed(lvmPattern, lvm, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's LVM setting: %v", err) @@ -1367,6 +1370,7 @@ func setGrubCfgLuksUUID(grubPath, uuid string) (err error) { luksUUID = fmt.Sprintf("%v%v", luksUUIDPrefix, uuid) } + logger.Log.Debugf("Adding luks('%s') to %s", luksUUID, grubPath) err = sed(luksUUIDPattern, luksUUID, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's luksUUID: %v", err) @@ -1382,6 +1386,7 @@ func setGrubCfgBootUUID(bootUUID, grubPath string) (err error) { ) var cmdline configuration.KernelCommandLine + logger.Log.Debugf("Adding UUID('%s') to %s", bootUUID, grubPath) err = sed(bootUUIDPattern, bootUUID, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's bootUUID: %v", err) @@ -1398,6 +1403,7 @@ func setGrubCfgEncryptedVolume(grubPath string) (err error) { var cmdline configuration.KernelCommandLine encryptedVol := fmt.Sprintf("%v%v%v%v", "(", lvmPrefix, diskutils.GetEncryptedRootVol(), ")") + logger.Log.Debugf("Adding EncryptedVolume('%s') to %s", encryptedVol, grubPath) err = sed(encryptedVolPattern, encryptedVol, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to grub.cfg's encryptedVolume: %v", err) @@ -1416,6 +1422,7 @@ func setGrubCfgRootDevice(rootDevice, grubPath, luksUUID string) (err error) { rootDevice = diskutils.GetEncryptedRootVolMapping() } + logger.Log.Debugf("Adding RootDevice('%s') to %s", rootDevice, grubPath) err = sed(rootDevicePattern, rootDevice, cmdline.GetSedDelimeter(), grubPath) if err != nil { logger.Log.Warnf("Failed to set grub.cfg's rootDevice: %v", err) From abe57caefedcd3c02ea252e8e1f5881200ea9e8b Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Wed, 23 Sep 2020 15:20:42 -0700 Subject: [PATCH 4/5] Update toolkit/tools/imagegen/installutils/installutils.go Co-authored-by: Christopher Co --- toolkit/tools/imagegen/installutils/installutils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/tools/imagegen/installutils/installutils.go b/toolkit/tools/imagegen/installutils/installutils.go index e2119b18a5b..4f50e37e75e 100644 --- a/toolkit/tools/imagegen/installutils/installutils.go +++ b/toolkit/tools/imagegen/installutils/installutils.go @@ -738,7 +738,7 @@ func InstallGrubCfg(installRoot, rootDevice, bootUUID string, encryptedRoot disk } // Append any additional command line parameters - err = setGrubCfgAdditional(installGrubCfgFile, kernelCommandLine) + err = setGrubCfgAdditionalCmdLine(installGrubCfgFile, kernelCommandLine) if err != nil { logger.Log.Warnf("Failed to append extra command line parameterse in grub.cfg: %v", err) return From c35b99031d0cc47c284e627b6d28fee38f42d1b4 Mon Sep 17 00:00:00 2001 From: Daniel McIlvaney Date: Wed, 23 Sep 2020 15:20:52 -0700 Subject: [PATCH 5/5] Update toolkit/tools/imagegen/installutils/installutils.go Co-authored-by: Christopher Co --- toolkit/tools/imagegen/installutils/installutils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/tools/imagegen/installutils/installutils.go b/toolkit/tools/imagegen/installutils/installutils.go index 4f50e37e75e..82c2ff32efd 100644 --- a/toolkit/tools/imagegen/installutils/installutils.go +++ b/toolkit/tools/imagegen/installutils/installutils.go @@ -1301,7 +1301,7 @@ func runPostInstallScripts(installChroot *safechroot.Chroot, config configuratio return } -func setGrubCfgAdditional(grubPath string, kernelCommandline configuration.KernelCommandLine) (err error) { +func setGrubCfgAdditionalCmdLine(grubPath string, kernelCommandline configuration.KernelCommandLine) (err error) { const ( extraPattern = "{{.ExtraCommandLine}}" )