From cbfc52406058e5c5e999badac510d4feee9c96d1 Mon Sep 17 00:00:00 2001 From: Jiankun Lu Date: Thu, 15 Sep 2022 18:09:01 -0700 Subject: [PATCH 1/2] Launcher: Add isHardened to LaunchSpec Add kernel cmd through image building to indicate if this is hardened image. LaunchSpec will have a new field to indicate the current env is hardened or not. * Rename LauncherSpec to LaunchSpec * HostTokenPath -> hostTokenPath * Silence openssl deprecation warnings for ms-tpm-20-ref Signed-off-by: Jiankun Lu --- launcher/container_runner.go | 19 +++---- launcher/container_runner_test.go | 12 ++--- launcher/image/preload.sh | 4 +- launcher/main.go | 7 ++- launcher/spec/launch_policy.go | 4 +- launcher/spec/launch_policy_test.go | 12 ++--- .../spec/{launcher_spec.go => launch_spec.go} | 51 ++++++++++++++----- ...ncher_spec_test.go => launch_spec_test.go} | 24 ++++----- simulator/internal/internal_cgo.go | 2 + 9 files changed, 82 insertions(+), 53 deletions(-) rename launcher/spec/{launcher_spec.go => launch_spec.go} (77%) rename launcher/spec/{launcher_spec_test.go => launch_spec_test.go} (83%) diff --git a/launcher/container_runner.go b/launcher/container_runner.go index 9ddbc5ddc..1ce6c9d6c 100644 --- a/launcher/container_runner.go +++ b/launcher/container_runner.go @@ -40,15 +40,16 @@ import ( // ContainerRunner contains information about the container settings type ContainerRunner struct { container containerd.Container - launchSpec spec.LauncherSpec + launchSpec spec.LaunchSpec attestConn *grpc.ClientConn attestAgent agent.AttestationAgent logger *log.Logger } const ( - // HostTokenPath defined the directory that will store attestation tokens - HostTokenPath = "/tmp/container_launcher/" + // hostTokenPath defined the directory in the host that will store attestation tokens + hostTokenPath = "/tmp/container_launcher/" + // containerTokenMountPath defined the directory in the container stores attestation tokens containerTokenMountPath = "/run/container_launcher/" attestationVerifierTokenFile = "attestation_verifier_claims_token" ) @@ -82,7 +83,7 @@ func fetchImpersonatedToken(ctx context.Context, serviceAccount string, audience } // NewRunner returns a runner. -func NewRunner(ctx context.Context, cdClient *containerd.Client, token oauth2.Token, launchSpec spec.LauncherSpec, mdsClient *metadata.Client, tpm io.ReadWriteCloser, logger *log.Logger) (*ContainerRunner, error) { +func NewRunner(ctx context.Context, cdClient *containerd.Client, token oauth2.Token, launchSpec spec.LaunchSpec, mdsClient *metadata.Client, tpm io.ReadWriteCloser, logger *log.Logger) (*ContainerRunner, error) { image, err := initImage(ctx, cdClient, launchSpec, token, logger) if err != nil { return nil, err @@ -232,7 +233,7 @@ func getGRPCClient(asAddr string, logger *log.Logger) (verifier.Client, *grpc.Cl // getRESTClient returns a REST verifier.Client that points to the given address. // It defaults to the Attestation Verifier instance at // https://confidentialcomputing.googleapis.com. -func getRESTClient(ctx context.Context, asAddr string, spec spec.LauncherSpec) (verifier.Client, error) { +func getRESTClient(ctx context.Context, asAddr string, spec spec.LaunchSpec) (verifier.Client, error) { httpClient, err := google.DefaultClient(ctx) if err != nil { return nil, fmt.Errorf("failed to create HTTP client: %v", err) @@ -265,7 +266,7 @@ func appendTokenMounts(mounts []specs.Mount) []specs.Mount { m := specs.Mount{} m.Destination = containerTokenMountPath m.Type = "bind" - m.Source = HostTokenPath + m.Source = hostTokenPath m.Options = []string{"rbind", "ro"} return append(mounts, m) @@ -348,7 +349,7 @@ func (r *ContainerRunner) refreshToken(ctx context.Context) (time.Duration, erro return 0, errors.New("token is expired") } - filepath := path.Join(HostTokenPath, attestationVerifierTokenFile) + filepath := path.Join(hostTokenPath, attestationVerifierTokenFile) if err = os.WriteFile(filepath, token, 0644); err != nil { return 0, fmt.Errorf("failed to write token to container mount source point: %v", err) } @@ -370,7 +371,7 @@ func (r *ContainerRunner) refreshToken(ctx context.Context) (time.Duration, erro // ctx must be a cancellable context. func (r *ContainerRunner) fetchAndWriteToken(ctx context.Context) error { - if err := os.MkdirAll(HostTokenPath, 0744); err != nil { + if err := os.MkdirAll(hostTokenPath, 0744); err != nil { return err } duration, err := r.refreshToken(ctx) @@ -451,7 +452,7 @@ func (r *ContainerRunner) Run(ctx context.Context) error { return nil } -func initImage(ctx context.Context, cdClient *containerd.Client, launchSpec spec.LauncherSpec, token oauth2.Token, logger *log.Logger) (containerd.Image, error) { +func initImage(ctx context.Context, cdClient *containerd.Client, launchSpec spec.LaunchSpec, token oauth2.Token, logger *log.Logger) (containerd.Image, error) { var remoteOpt containerd.RemoteOpt if token.Valid() { remoteOpt = containerd.WithResolver(Resolver(token.AccessToken)) diff --git a/launcher/container_runner_test.go b/launcher/container_runner_test.go index a42df7280..c6d7832e2 100644 --- a/launcher/container_runner_test.go +++ b/launcher/container_runner_test.go @@ -88,7 +88,7 @@ func TestRefreshToken(t *testing.T) { logger: log.Default(), } - if err := os.MkdirAll(HostTokenPath, 0744); err != nil { + if err := os.MkdirAll(hostTokenPath, 0744); err != nil { t.Fatalf("Error creating host token path directory: %v", err) } @@ -97,7 +97,7 @@ func TestRefreshToken(t *testing.T) { t.Fatalf("refreshToken returned with error: %v", err) } - filepath := path.Join(HostTokenPath, attestationVerifierTokenFile) + filepath := path.Join(hostTokenPath, attestationVerifierTokenFile) data, err := os.ReadFile(filepath) if err != nil { t.Fatalf("Failed to read from %s: %v", filepath, err) @@ -114,7 +114,7 @@ func TestRefreshToken(t *testing.T) { } func TestRefreshTokenError(t *testing.T) { - if err := os.MkdirAll(HostTokenPath, 0744); err != nil { + if err := os.MkdirAll(hostTokenPath, 0744); err != nil { t.Fatalf("Error creating host token path directory: %v", err) } @@ -174,7 +174,7 @@ func TestFetchAndWriteTokenSucceeds(t *testing.T) { t.Fatalf("fetchAndWriteToken failed: %v", err) } - filepath := path.Join(HostTokenPath, attestationVerifierTokenFile) + filepath := path.Join(hostTokenPath, attestationVerifierTokenFile) data, err := os.ReadFile(filepath) if err != nil { t.Fatalf("Failed to read from %s: %v", filepath, err) @@ -208,7 +208,7 @@ func TestTokenIsNotChangedIfRefreshFails(t *testing.T) { t.Fatalf("fetchAndWriteToken failed: %v", err) } - filepath := path.Join(HostTokenPath, attestationVerifierTokenFile) + filepath := path.Join(hostTokenPath, attestationVerifierTokenFile) data, err := os.ReadFile(filepath) if err != nil { t.Fatalf("Failed to read from %s: %v", filepath, err) @@ -254,7 +254,7 @@ func TestFetchAndWriteTokenWithTokenRefresh(t *testing.T) { t.Fatalf("fetchAndWriteToken failed: %v", err) } - filepath := path.Join(HostTokenPath, attestationVerifierTokenFile) + filepath := path.Join(hostTokenPath, attestationVerifierTokenFile) data, err := os.ReadFile(filepath) if err != nil { t.Fatalf("Failed to read from %s: %v", filepath, err) diff --git a/launcher/image/preload.sh b/launcher/image/preload.sh index 5472eb196..ac8c22211 100644 --- a/launcher/image/preload.sh +++ b/launcher/image/preload.sh @@ -60,7 +60,7 @@ configure_necessary_systemd_units() { configure_systemd_units_for_debug() { # No-op for now, as debug will default to using multi-user.target. - exit 0 + : } configure_systemd_units_for_hardened() { @@ -91,8 +91,10 @@ main() { if [[ "${IMAGE_ENV}" == "debug" ]]; then configure_systemd_units_for_debug + append_cmdline "'confidential-space.hardened=false'" elif [[ "${IMAGE_ENV}" == "hardened" ]]; then configure_systemd_units_for_hardened + append_cmdline "'confidential-space.hardened=true'" else echo "Unknown image env: ${IMAGE_ENV}." \ "Only 'debug' and 'hardened' are supported." diff --git a/launcher/main.go b/launcher/main.go index 52ea321d3..c1de57130 100644 --- a/launcher/main.go +++ b/launcher/main.go @@ -48,13 +48,12 @@ func run() int { logger.SetOutput(loggerAndStdout) } - spec, err := spec.GetLauncherSpec(mdsClient) + launchSpec, err := spec.GetLaunchSpec(mdsClient) if err != nil { logger.Println(err) return 1 } - - logger.Println("Launcher Spec: ", spec) + logger.Println("Launch Spec: ", launchSpec) client, err := containerd.New(defaults.DefaultAddress) if err != nil { @@ -75,7 +74,7 @@ func run() int { logger.Printf("failed to retrieve auth token: %v, using empty auth", err) } - r, err := NewRunner(ctx, client, token, spec, mdsClient, tpm, logger) + r, err := NewRunner(ctx, client, token, launchSpec, mdsClient, tpm, logger) if err != nil { logger.Println(err) return 1 diff --git a/launcher/spec/launch_policy.go b/launcher/spec/launch_policy.go index 4569e3067..aa3dc8bb5 100644 --- a/launcher/spec/launch_policy.go +++ b/launcher/spec/launch_policy.go @@ -42,9 +42,9 @@ func GetLaunchPolicy(imageLabels map[string]string) (LaunchPolicy, error) { return launchPolicy, nil } -// Verify will use the LaunchPolicy to verify the given LauncherSpec. If the verification passed, will return nil. +// Verify will use the LaunchPolicy to verify the given LaunchSpec. If the verification passed, will return nil. // If there are multiple violations, the function will return the first error. -func (p LaunchPolicy) Verify(ls LauncherSpec) error { +func (p LaunchPolicy) Verify(ls LaunchSpec) error { for _, e := range ls.Envs { if !contains(p.AllowedEnvOverride, e.Name) { return fmt.Errorf("env var %s is not allowed to be overridden on this image; allowed envs to be overridden: %v", e, p.AllowedEnvOverride) diff --git a/launcher/spec/launch_policy_test.go b/launcher/spec/launch_policy_test.go index cc9d786fd..918e5e567 100644 --- a/launcher/spec/launch_policy_test.go +++ b/launcher/spec/launch_policy_test.go @@ -72,7 +72,7 @@ func TestVerify(t *testing.T) { testCases := []struct { testName string policy LaunchPolicy - spec LauncherSpec + spec LaunchSpec expectErr bool }{ { @@ -81,7 +81,7 @@ func TestVerify(t *testing.T) { AllowedEnvOverride: []string{"foo"}, AllowedCmdOverride: true, }, - LauncherSpec{ + LaunchSpec{ Envs: []EnvVar{{Name: "foo", Value: "foo"}}, Cmd: []string{"foo"}, }, @@ -90,7 +90,7 @@ func TestVerify(t *testing.T) { { "default case", LaunchPolicy{}, - LauncherSpec{}, + LaunchSpec{}, false, }, { @@ -98,7 +98,7 @@ func TestVerify(t *testing.T) { LaunchPolicy{ AllowedEnvOverride: []string{"foo"}, }, - LauncherSpec{ + LaunchSpec{ Envs: []EnvVar{{Name: "bar", Value: ""}}, }, true, @@ -108,7 +108,7 @@ func TestVerify(t *testing.T) { LaunchPolicy{ AllowedCmdOverride: false, }, - LauncherSpec{ + LaunchSpec{ Cmd: []string{"foo"}, }, true, @@ -119,7 +119,7 @@ func TestVerify(t *testing.T) { AllowedEnvOverride: []string{"foo"}, AllowedCmdOverride: true, }, - LauncherSpec{ + LaunchSpec{ Envs: []EnvVar{{Name: "foo", Value: "foo"}}, Cmd: []string{"foo"}, }, diff --git a/launcher/spec/launcher_spec.go b/launcher/spec/launch_spec.go similarity index 77% rename from launcher/spec/launcher_spec.go rename to launcher/spec/launch_spec.go index a3bd977da..4b5a2e09f 100644 --- a/launcher/spec/launcher_spec.go +++ b/launcher/spec/launch_spec.go @@ -1,10 +1,11 @@ -// Package spec contains definition of some basic container launcher specs needed to +// Package spec contains definition of some basic container launch specs needed to // launch a container, provided by the operator. package spec import ( "encoding/json" "fmt" + "os" "strings" "cloud.google.com/go/compute/metadata" @@ -49,9 +50,9 @@ type EnvVar struct { Value string } -// LauncherSpec contains specification set by the operator who wants to +// LaunchSpec contains specification set by the operator who wants to // launch a container. -type LauncherSpec struct { +type LaunchSpec struct { // MDS-based values. ImageRef string RestartPolicy RestartPolicy @@ -61,11 +62,12 @@ type LauncherSpec struct { ImpersonateServiceAccounts []string ProjectID string Region string + Hardened bool } // UnmarshalJSON unmarshals an instance attributes list in JSON format from the metadata -// server set by an operator to a LauncherSpec. -func (s *LauncherSpec) UnmarshalJSON(b []byte) error { +// server set by an operator to a LaunchSpec. +func (s *LaunchSpec) UnmarshalJSON(b []byte) error { var unmarshaledMap map[string]string if err := json.Unmarshal(b, &unmarshaledMap); err != nil { return err @@ -121,30 +123,53 @@ func getRegion(client *metadata.Client) (string, error) { return zone[:lastDash], nil } -// GetLauncherSpec takes in a metadata server client, reads and parse operator's -// input to the GCE instance custom metadata and return a LauncherSpec. +// GetLaunchSpec takes in a metadata server client, reads and parse operator's +// input to the GCE instance custom metadata and return a LaunchSpec. // ImageRef (tee-image-reference) is required, will return an error if // ImageRef is not presented in the metadata. -func GetLauncherSpec(client *metadata.Client) (LauncherSpec, error) { +func GetLaunchSpec(client *metadata.Client) (LaunchSpec, error) { data, err := client.Get(instanceAttributesQuery) if err != nil { - return LauncherSpec{}, err + return LaunchSpec{}, err } - spec := &LauncherSpec{} + spec := &LaunchSpec{} if err := spec.UnmarshalJSON([]byte(data)); err != nil { - return LauncherSpec{}, err + return LaunchSpec{}, err } spec.ProjectID, err = client.ProjectID() if err != nil { - return LauncherSpec{}, fmt.Errorf("failed to retrieve projectID from MDS: %v", err) + return LaunchSpec{}, fmt.Errorf("failed to retrieve projectID from MDS: %v", err) } spec.Region, err = getRegion(client) if err != nil { - return LauncherSpec{}, err + return LaunchSpec{}, err } + kernelCmd, err := readCmdline() + if err != nil { + return LaunchSpec{}, err + } + spec.Hardened = isHardened(kernelCmd) + return *spec, nil } + +func isHardened(kernelCmd string) bool { + for _, arg := range strings.Fields(kernelCmd) { + if arg == "confidential-space.hardened=true" { + return true + } + } + return false +} + +func readCmdline() (string, error) { + kernelCmd, err := os.ReadFile("/proc/cmdline") + if err != nil { + return "", err + } + return string(kernelCmd), nil +} diff --git a/launcher/spec/launcher_spec_test.go b/launcher/spec/launch_spec_test.go similarity index 83% rename from launcher/spec/launcher_spec_test.go rename to launcher/spec/launch_spec_test.go index 39a26d25c..8f78797e5 100644 --- a/launcher/spec/launcher_spec_test.go +++ b/launcher/spec/launch_spec_test.go @@ -6,7 +6,7 @@ import ( "github.com/google/go-cmp/cmp" ) -func TestLauncherSpecUnmarshalJSONHappyCases(t *testing.T) { +func TestLaunchSpecUnmarshalJSONHappyCases(t *testing.T) { var testCases = []struct { testName string mdsJSON string @@ -35,7 +35,7 @@ func TestLauncherSpecUnmarshalJSONHappyCases(t *testing.T) { }, } - want := &LauncherSpec{ + want := &LaunchSpec{ ImageRef: "docker.io/library/hello-world:latest", RestartPolicy: Always, Cmd: []string{"--foo", "--bar", "--baz"}, @@ -45,18 +45,18 @@ func TestLauncherSpecUnmarshalJSONHappyCases(t *testing.T) { for _, testcase := range testCases { t.Run(testcase.testName, func(t *testing.T) { - spec := &LauncherSpec{} + spec := &LaunchSpec{} if err := spec.UnmarshalJSON([]byte(testcase.mdsJSON)); err != nil { t.Fatal(err) } if !cmp.Equal(spec, want) { - t.Errorf("LauncherSpec UnmarshalJSON got %+v, want %+v", spec, want) + t.Errorf("LaunchSpec UnmarshalJSON got %+v, want %+v", spec, want) } }) } } -func TestLauncherSpecUnmarshalJSONBadInput(t *testing.T) { +func TestLaunchSpecUnmarshalJSONBadInput(t *testing.T) { var testCases = []struct { testName string mdsJSON string @@ -92,7 +92,7 @@ func TestLauncherSpecUnmarshalJSONBadInput(t *testing.T) { for _, testcase := range testCases { t.Run(testcase.testName, func(t *testing.T) { - spec := &LauncherSpec{} + spec := &LaunchSpec{} if err := spec.UnmarshalJSON([]byte(testcase.mdsJSON)); err == nil { t.Fatal("expected JSON parsing err") } @@ -100,35 +100,35 @@ func TestLauncherSpecUnmarshalJSONBadInput(t *testing.T) { } } -func TestLauncherSpecUnmarshalJSONWithDefaultValue(t *testing.T) { +func TestLaunchSpecUnmarshalJSONWithDefaultValue(t *testing.T) { mdsJSON := `{ "tee-image-reference":"docker.io/library/hello-world:latest", "tee-impersonate-service-accounts":"" }` - spec := &LauncherSpec{} + spec := &LaunchSpec{} if err := spec.UnmarshalJSON([]byte(mdsJSON)); err != nil { t.Fatal(err) } - want := &LauncherSpec{ + want := &LaunchSpec{ ImageRef: "docker.io/library/hello-world:latest", RestartPolicy: Never, } if !cmp.Equal(spec, want) { - t.Errorf("LauncherSpec UnmarshalJSON got %+v, want %+v", spec, want) + t.Errorf("LaunchSpec UnmarshalJSON got %+v, want %+v", spec, want) } } -func TestLauncherSpecUnmarshalJSONWithoutImageReference(t *testing.T) { +func TestLaunchSpecUnmarshalJSONWithoutImageReference(t *testing.T) { mdsJSON := `{ "tee-cmd":"[\"--foo\",\"--bar\",\"--baz\"]", "tee-env-foo":"bar", "tee-restart-policy":"Never" }` - spec := &LauncherSpec{} + spec := &LaunchSpec{} if err := spec.UnmarshalJSON([]byte(mdsJSON)); err == nil || err != errImageRefNotSpecified { t.Fatalf("got %v error, but expected %v error", err, errImageRefNotSpecified) } diff --git a/simulator/internal/internal_cgo.go b/simulator/internal/internal_cgo.go index 0d080b53d..ad7463554 100644 --- a/simulator/internal/internal_cgo.go +++ b/simulator/internal/internal_cgo.go @@ -18,6 +18,8 @@ package internal // #cgo !windows CFLAGS: -fstack-protector-all // // Silence known warnings from the reference code and CGO code. // #cgo CFLAGS: -Wno-missing-braces -Wno-empty-body -Wno-unused-variable -Wno-uninitialized +// // Silence openssl deprecation warnings for ms-tpm-20-ref +// #cgo CFLAGS: -Wno-deprecated-declarations // // Link against the system OpenSSL // #cgo CFLAGS: -DDEBUG=YES // #cgo CFLAGS: -DSIMULATION=NO From aa2f16868f988ef08a5cd82c30b37263e8a26c67 Mon Sep 17 00:00:00 2001 From: Jiankun Lu Date: Thu, 15 Sep 2022 18:17:57 -0700 Subject: [PATCH 2/2] Add isHardened test Signed-off-by: Jiankun Lu --- launcher/spec/launch_policy_test.go | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/launcher/spec/launch_policy_test.go b/launcher/spec/launch_policy_test.go index 918e5e567..f1f1a371e 100644 --- a/launcher/spec/launch_policy_test.go +++ b/launcher/spec/launch_policy_test.go @@ -141,3 +141,41 @@ func TestVerify(t *testing.T) { }) } } + +func TestIsHardened(t *testing.T) { + testCases := []struct { + testName string + kernelCmd string + expectHardened bool + }{ + { + "empty kernel cmd", + "", + false, + }, + { + "no confidential-space.hardened arg", + "BOOT_IMAGE=/syslinux/vmlinuz.B init=/usr/lib/systemd/systemd boot=local rootwait ro noresume loglevel=7 console=tty1 console=ttyS0 security=apparmor virtio_net.napi_tx=1 nmi_watchdog=0 csm.disabled=1 loadpin.exclude=kernel-module modules-load=loadpin_trigger module.sig_enforce=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi cos.protected_stateful_partition=e systemd.mask=update-engine.service ds=nocloud;s=/usr/share/oem/ cros_debug root=/dev/dm-0 \"dm=2 vroot none ro 1,0 4077568 verity payload=PARTUUID=DC7DB0DC-DDCC-AA45-BAE3-A41CA1698E83 hashtree=PARTUUID=DC7DB0DC-DDCC-AA45-BAE3-A41CA1698E83 hashstart=4077568 alg=sha256 root_hexdigest=6d5887660805db1b366319bd1c2161600d11b9e53f059b0e44b760a7277e1b0a salt=f4a41993832655a00d48f5769351370bebafd7de906df068bc1b1929b175ee43,oemroot none ro 1, 0 1024000 verity payload=PARTUUID=fd5af56a-7b25-c448-a616-19eb240b3260 hashtree=PARTUUID=fd5af56a-7b25-c448-a616-19eb240b3260 hashstart=1024000 alg=sha256 root_hexdigest=50c406c129054649a432fa144eeff56aa8b707d4c86f3ab44edde589356e8b23 salt=2a3461269a26ad6247f4b64cacd84f64e5a3311cd4b2f742bab6442291bf4977\"", + false, + }, + { + "has kernel arg confidential-space.hardened=true", + "BOOT_IMAGE=/syslinux/vmlinuz.B init=/usr/lib/systemd/systemd boot=local rootwait ro noresume loglevel=7 console=tty1 console=ttyS0 security=apparmor virtio_net.napi_tx=1 nmi_watchdog=0 csm.disabled=1 loadpin.exclude=kernel-module modules-load=loadpin_trigger module.sig_enforce=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi confidential-space.hardened=true cos.protected_stateful_partition=e systemd.mask=update-engine.service ds=nocloud;s=/usr/share/oem/ cros_debug root=/dev/dm-0 \"dm=2 vroot none ro 1,0 4077568 verity payload=PARTUUID=DC7DB0DC-DDCC-AA45-BAE3-A41CA1698E83 hashtree=PARTUUID=DC7DB0DC-DDCC-AA45-BAE3-A41CA1698E83 hashstart=4077568 alg=sha256 root_hexdigest=6d5887660805db1b366319bd1c2161600d11b9e53f059b0e44b760a7277e1b0a salt=f4a41993832655a00d48f5769351370bebafd7de906df068bc1b1929b175ee43,oemroot none ro 1, 0 1024000 verity payload=PARTUUID=fd5af56a-7b25-c448-a616-19eb240b3260 hashtree=PARTUUID=fd5af56a-7b25-c448-a616-19eb240b3260 hashstart=1024000 alg=sha256 root_hexdigest=50c406c129054649a432fa144eeff56aa8b707d4c86f3ab44edde589356e8b23 salt=2a3461269a26ad6247f4b64cacd84f64e5a3311cd4b2f742bab6442291bf4977\"", + true, + }, + { + "has kernel arg confidential-space.hardened=false", + "BOOT_IMAGE=/syslinux/vmlinuz.B init=/usr/lib/systemd/systemd boot=local rootwait ro noresume loglevel=7 console=tty1 console=ttyS0 security=apparmor virtio_net.napi_tx=1 nmi_watchdog=0 csm.disabled=1 loadpin.exclude=kernel-module modules-load=loadpin_trigger module.sig_enforce=1 dm_verity.error_behavior=3 dm_verity.max_bios=-1 dm_verity.dev_wait=1 i915.modeset=1 cros_efi confidential-space.hardened=false cos.protected_stateful_partition=e systemd.mask=update-engine.service ds=nocloud;s=/usr/share/oem/ cros_debug root=/dev/dm-0 \"dm=2 vroot none ro 1,0 4077568 verity payload=PARTUUID=DC7DB0DC-DDCC-AA45-BAE3-A41CA1698E83 hashtree=PARTUUID=DC7DB0DC-DDCC-AA45-BAE3-A41CA1698E83 hashstart=4077568 alg=sha256 root_hexdigest=6d5887660805db1b366319bd1c2161600d11b9e53f059b0e44b760a7277e1b0a salt=f4a41993832655a00d48f5769351370bebafd7de906df068bc1b1929b175ee43,oemroot none ro 1, 0 1024000 verity payload=PARTUUID=fd5af56a-7b25-c448-a616-19eb240b3260 hashtree=PARTUUID=fd5af56a-7b25-c448-a616-19eb240b3260 hashstart=1024000 alg=sha256 root_hexdigest=50c406c129054649a432fa144eeff56aa8b707d4c86f3ab44edde589356e8b23 salt=2a3461269a26ad6247f4b64cacd84f64e5a3311cd4b2f742bab6442291bf4977\"", + false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.testName, func(t *testing.T) { + hardened := isHardened(testCase.kernelCmd) + if testCase.expectHardened != hardened { + t.Errorf("expected %t, but got %t", testCase.expectHardened, hardened) + } + }) + } +}