blob: 5dc8841e84f16fa2afaf6a832049be1afeb18145 [file] [log] [blame]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package policy
import (
"context"
"fmt"
"os"
"time"
"go.chromium.org/tast-tests/cros/common/fixture"
"go.chromium.org/tast-tests/cros/common/policy"
"go.chromium.org/tast-tests/cros/common/tape"
"go.chromium.org/tast-tests/cros/common/testexec"
"go.chromium.org/tast-tests/cros/remote/dutfs"
"go.chromium.org/tast-tests/cros/services/cros/platform"
ps "go.chromium.org/tast-tests/cros/services/cros/policy"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/rpc"
"go.chromium.org/tast/core/testing"
)
const (
flexConfigInitialDirPath = "/mnt/stateful_partition/unencrypted/flex_config"
flexConfigInitialFilePath = "/mnt/stateful_partition/unencrypted/flex_config/config.json"
// Path that flex_config will be in after moved to encrypted stateful partition (ESP)
// on oobe_config_restore startup.
flexConfigESPDirPath = "/var/lib/oobe_config_restore/flex_config"
flexConfigESPFilePath = "/var/lib/oobe_config_restore/flex_config/config.json"
enrollmentTokenVarCEU = "policy.TokenBasedEnrollment.enrollment_token"
enrollmentTokenVarKiosk = "policy.TokenBasedEnrollment.enrollment_token_kiosk"
oobeConfigRestoreUID = "20121"
oobeConfigRestoreGID = "20121"
enrollmentTimeout = 5 * time.Minute
)
type tokenEnrollmentParams struct {
// Variable name of enrollment token used to enroll. Different tokens may
// be in different OUs, may be revoked, or may be associated with different
// upgrade types.
enrollmentTokenVar string
// URL defining which DMServer environment to talk to during enrollment.
dmServerURL string
}
func init() {
testing.AddTest(&testing.Test{
Func: TokenBasedEnrollment,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Verify success of token-based enrollment on ChromeOS Flex",
Contacts: []string{
"cros-onboarding-team@google.com",
"jacksontadie@google.com",
},
BugComponent: "b:1271043", // Chrome OS Server Projects > Enterprise Management >> Chrome Commercial Backend >> Onboarding >> Enterprise Enrollment
Fixture: fixture.CleanOwnership,
Attr: []string{"group:dmserver-enrollment-daily"},
SoftwareDeps: []string{"reven_oobe_config", "chrome"},
ServiceDeps: []string{
"tast.cros.policy.PolicyService",
dutfs.ServiceName,
"tast.cros.tape.Service",
"tast.cros.platform.UpstartService",
},
Timeout: enrollmentTimeout,
VarDeps: []string{
enrollmentTokenVarCEU,
enrollmentTokenVarKiosk,
tape.ServiceAccountVar,
"ui.signinProfileTestExtensionManifestKey"},
Params: []testing.Param{
{
Name: "autopush",
Val: tokenEnrollmentParams{
dmServerURL: policy.DMServerAlphaURL,
enrollmentTokenVar: enrollmentTokenVarCEU,
},
// TODO b/346725308 Refactor to use utility and known dependency list.
ExtraSearchFlags: []*testing.StringPair{{
Key: "external_dependency", Value: "DMServerAlpha",
}},
}, {
Name: "autopush_kiosk",
Val: tokenEnrollmentParams{
dmServerURL: policy.DMServerAlphaURL,
enrollmentTokenVar: enrollmentTokenVarKiosk,
},
// TODO b/346725308 Refactor to use utility and known dependency list.
ExtraSearchFlags: []*testing.StringPair{{
Key: "external_dependency", Value: "DMServerAlpha",
}},
},
},
})
}
func TokenBasedEnrollment(ctx context.Context, s *testing.State) {
params := s.Param().(tokenEnrollmentParams)
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(cleanupCtx, 20*time.Second)
defer cancel()
cl, err := rpc.Dial(ctx, s.DUT(), s.RPCHint())
if err != nil {
s.Fatal("Failed to connect to the RPC service on the DUT: ", err)
}
defer cl.Close(ctx)
fs := dutfs.NewClient(cl.Conn)
defer func() {
s.Log("Cleaning up Flex config dirs")
if err := cleanUpFlexConfigDirs(cleanupCtx, fs); err != nil {
s.Error("Failed to clean up Flex config dirs: ", err)
}
}()
// Create Flex config dir and write enrollmentToken JSON to config file within.
if err := fs.MkDir(ctx, flexConfigInitialDirPath, 0740); err != nil {
s.Fatalf("Failed to create %s directory: %v", flexConfigInitialDirPath, err)
}
enrollmentToken := s.RequiredVar(params.enrollmentTokenVar)
oobeConfigJSON := fmt.Sprintf("{ \"enrollmentToken\": \"%s\" }", enrollmentToken)
if err := fs.WriteFile(ctx, flexConfigInitialFilePath, []byte(oobeConfigJSON), 0640); err != nil {
s.Fatalf("Failed to create %s file: %v", flexConfigInitialFilePath, err)
}
// Chown Flex config dir so oobe_config_restore daemon can read/write it.
chownArgs := []string{"-R", oobeConfigRestoreUID + ":" + oobeConfigRestoreGID, flexConfigInitialDirPath}
if err := s.DUT().Conn().CommandContext(ctx, "chown", chownArgs...).Run(testexec.DumpLogOnError); err != nil {
s.Fatalf("Failed to chown %s and its contents: %v", flexConfigInitialDirPath, err)
}
tapeClient, err := tape.NewClient(ctx, []byte(s.RequiredVar(tape.ServiceAccountVar)))
if err != nil {
s.Fatal("Failed to create tape client: ", err)
}
defer func(ctx context.Context) {
if err := tapeClient.DeprovisionHelper(ctx, cl, "unused"); err != nil {
s.Fatal("Failed to deprovision device: ", err)
}
}(cleanupCtx)
// Have to restart job after creating flex_config files as upstart config conditionally
// bind-mounts the flex_config dir only if it's present.
upstartService := platform.NewUpstartServiceClient(cl.Conn)
if _, err := upstartService.RestartJob(ctx, &platform.RestartJobRequest{JobName: "oobe_config_restore"}); err != nil {
s.Fatal("Failed to restart oobe_config_restore daemon: ", err)
}
// Verify flex_config has been migrated to encrypted stateful partition after
// oobe_config_restore restart.
exists, err := pathExists(ctx, fs, flexConfigInitialFilePath)
if err != nil {
s.Fatal("Failed to check existence of Flex config file in unencrypted stateful partition: ", err)
}
if exists {
s.Fatal("Flex config has not been deleted from unencrypted stateful partition")
}
exists, err = pathExists(ctx, fs, flexConfigESPFilePath)
if err != nil {
s.Fatal("Failed to check existence of Flex config file in encrypted stateful partition: ", err)
}
if !exists {
s.Fatal("Flex config has not been moved to encrypted stateful partition")
}
policyClient := ps.NewPolicyServiceClient(cl.Conn)
if _, err := policyClient.TokenBasedEnrollUsingChrome(ctx, &ps.TokenBasedEnrollUsingChromeRequest{
DmserverURL: params.dmServerURL,
ManifestKey: s.RequiredVar("ui.signinProfileTestExtensionManifestKey"),
}); err != nil {
s.Fatal("Failed to enroll using enrollment token: ", err)
}
exists, err = pathExists(ctx, fs, flexConfigESPFilePath)
if err != nil {
s.Fatal("Failed to check existence of Flex config file in encrypted stateful partition: ", err)
}
if exists {
s.Fatal("Flex config was not cleaned up after enrollment")
}
}
func cleanUpFlexConfigDirs(ctx context.Context, fs *dutfs.Client) error {
if err := fs.RemoveAll(ctx, flexConfigInitialDirPath); err != nil {
return errors.Wrapf(err, "failed to delete %s directory", flexConfigInitialDirPath)
}
if err := fs.RemoveAll(ctx, flexConfigESPDirPath); err != nil {
return errors.Wrapf(err, "failed to delete %s directory", flexConfigESPDirPath)
}
return nil
}
func pathExists(ctx context.Context, fs *dutfs.Client, path string) (bool, error) {
_, err := fs.Stat(ctx, path)
if err != nil && !os.IsNotExist(err) {
return false, errors.Wrapf(err, "unexpected error when trying to stat %s", path)
}
return err == nil, nil
}