Skip to content

Commit

Permalink
Merge pull request google#244 from jkl73/checkhardenedminorfix
Browse files Browse the repository at this point in the history
Add IsHardened in launch spec
  • Loading branch information
jkl73 authored Sep 20, 2022
2 parents eb06ae8 + aa2f168 commit f599e6c
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 53 deletions.
19 changes: 10 additions & 9 deletions launcher/container_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
12 changes: 6 additions & 6 deletions launcher/container_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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)
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion launcher/image/preload.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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."
Expand Down
7 changes: 3 additions & 4 deletions launcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions launcher/spec/launch_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 44 additions & 6 deletions launcher/spec/launch_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestVerify(t *testing.T) {
testCases := []struct {
testName string
policy LaunchPolicy
spec LauncherSpec
spec LaunchSpec
expectErr bool
}{
{
Expand All @@ -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"},
},
Expand All @@ -90,15 +90,15 @@ func TestVerify(t *testing.T) {
{
"default case",
LaunchPolicy{},
LauncherSpec{},
LaunchSpec{},
false,
},
{
"env override violation",
LaunchPolicy{
AllowedEnvOverride: []string{"foo"},
},
LauncherSpec{
LaunchSpec{
Envs: []EnvVar{{Name: "bar", Value: ""}},
},
true,
Expand All @@ -108,7 +108,7 @@ func TestVerify(t *testing.T) {
LaunchPolicy{
AllowedCmdOverride: false,
},
LauncherSpec{
LaunchSpec{
Cmd: []string{"foo"},
},
true,
Expand All @@ -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"},
},
Expand All @@ -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)
}
})
}
}
51 changes: 38 additions & 13 deletions launcher/spec/launcher_spec.go → launcher/spec/launch_spec.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit f599e6c

Please sign in to comment.