package main import ( "context" "io/fs" "os" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/forgekey" ) // npmAsUmask077 installs as npm does under a umask of 077: node_modules with a package whose folders are 0700, its // file 0600 and its program 0700. func npmAsUmask077(f *fakeNpm) forgekey.Runner { return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { ran := f.run(ctx, env, name, args...) for i, a := range args { if a == "--prefix" { pkg := filepath.Join(args[i+1], "node_modules", "typescript", "bin") os.MkdirAll(pkg, 0o700) os.WriteFile(filepath.Join(args[i+1], "node_modules", "typescript", "package.json"), []byte("{}"), 0o600) os.WriteFile(filepath.Join(pkg, "tsc"), []byte("#!/bin/sh\n"), 0o700) } } return ran } } // checkOpen fails unless every folder from the cache's toolchain down lets others enter and read it and every file lets // others read it, a program others run, and nothing lets anyone but the owner write. func checkOpen(t *testing.T, tc Toolchain) { t.Helper() root := filepath.Join(tc.Paths.Cache, "toolchain") err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { if err != nil { return err } info, err := d.Info() if err != nil { return err } mode := info.Mode().Perm() switch { case mode&0o022 != 0: t.Errorf("%s is %o: writable by others than its owner", p, mode) case d.IsDir() && mode&0o005 != 0o005: t.Errorf("%s is %o: others may not enter and read it", p, mode) case !d.IsDir() && mode&0o004 == 0: t.Errorf("%s is %o: others may not read it", p, mode) case !d.IsDir() && mode&0o100 != 0 && mode&0o001 == 0: t.Errorf("%s is %o: a program others may not run", p, mode) } return nil }) if err != nil { t.Fatal(err) } } // 1891 (ADR 0533 §2): a toolchain the module installs can be read by the other build module's integration level: // its folders 0755 and its files 0644 (programs 0755), whatever umask npm ran under, and writable by its owner alone. func TestAnInstalledToolchainIsReadableByOthersAndWritableByItsOwnerAlone(t *testing.T) { fake := &fakeNpm{} tc := toolchainIn(t, fake) tc.Run = npmAsUmask077(fake) npm := filepath.Join(tc.Paths.Cache, "npm") if err := os.MkdirAll(npm, 0o700); err != nil { t.Fatal(err) } if err := tc.Ensure(context.Background(), "v1"); err != nil { t.Fatal(err) } checkOpen(t, tc) if st, err := os.Stat(npm); err != nil || st.Mode().Perm() != 0o700 { t.Errorf("the npm download cache beside the toolchains is %v (%v), want 0700: only the toolchains are opened", st.Mode().Perm(), err) } } // 1891 (ADR 0533 §2): a toolchain installed before this release, shut to its owner, is opened by the next Ensure // without installing it again. func TestAToolchainInstalledShutIsOpenedWithoutAnInstall(t *testing.T) { fake := &fakeNpm{} tc := toolchainIn(t, fake) dir := tc.Paths.Dir("v1") os.MkdirAll(filepath.Join(dir, "node_modules", "esbuild", "bin"), 0o700) os.WriteFile(filepath.Join(dir, "node_modules", "esbuild", "bin", "esbuild"), []byte("x"), 0o700) os.WriteFile(filepath.Join(dir, "package-lock.json"), []byte("{}"), 0o600) os.WriteFile(filepath.Join(dir, installedMark), []byte("v1\n"), 0o600) os.Chmod(filepath.Join(tc.Paths.Cache, "toolchain"), 0o700) if err := tc.Ensure(context.Background(), "v1"); err != nil { t.Fatal(err) } if len(fake.calls) != 0 { t.Errorf("npm ran %d times for a toolchain already there", len(fake.calls)) } checkOpen(t, tc) } // 1891 (ADR 0533 §2): the cache directory lets other accounts enter it and not list it, so the toolchains beneath it // can be reached by their path and the npm cache beside them (0700) cannot. func TestTheCacheDirectoryIsEnteredByOthersNotListed(t *testing.T) { r := resource(t, readManifest(t), "cache") if r["mode"] != "0711" { t.Errorf("the cache directory's mode is %v, want 0711", r["mode"]) } } // 1891 review (ADR 0533 §2): the registry credential npm is told is never written beneath the cache, so opening a // toolchain can never open it: npm reads it from a file in the account's own home, readable by its owner alone, and // the file is gone when Ensure returns. func TestTheInstallCredentialIsNeverInTheFolderThatIsOpened(t *testing.T) { fake := &fakeNpm{} tc := toolchainIn(t, fake) var userconfig string inner := npmAsUmask077(fake) tc.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { for i, a := range args { if a == "--userconfig" { userconfig = args[i+1] } } if st, err := os.Stat(userconfig); err != nil || st.Mode().Perm() != 0o600 { t.Errorf("the credential file %s is %v (%v), want 0600", userconfig, st.Mode().Perm(), err) } return inner(ctx, env, name, args...) } if err := tc.Ensure(context.Background(), "v1"); err != nil { t.Fatal(err) } if rel, err := filepath.Rel(tc.Paths.Home, userconfig); err != nil || rel != filepath.Base(userconfig) { t.Errorf("the credential file %s is not in the account's home %s", userconfig, tc.Paths.Home) } if _, err := os.Stat(userconfig); err == nil { t.Errorf("the credential file %s is left after the install", userconfig) } filepath.WalkDir(tc.Paths.Cache, func(p string, d fs.DirEntry, err error) error { if err == nil && strings.Contains(d.Name(), "npmrc") { t.Errorf("a credential file %s lies beneath the cache", p) } return nil }) } // 1891 review (ADR 0533 §2): open refuses a toolchain folder that holds an npm config file, naming it, and opens // nothing of it, so a credential left by a dead install is never made readable. func TestOpenRefusesAFolderHoldingAnNpmConfig(t *testing.T) { fake := &fakeNpm{} tc := toolchainIn(t, fake) dir := tc.Paths.Dir("v1") os.MkdirAll(filepath.Join(dir, "node_modules"), 0o700) left := filepath.Join(dir, ".npmrc-install") os.WriteFile(left, []byte("//x/:_auth=secret\n"), 0o600) os.WriteFile(filepath.Join(dir, installedMark), []byte("v1\n"), 0o600) err := tc.Ensure(context.Background(), "v1") if err == nil || !strings.Contains(err.Error(), left) { t.Errorf("Ensure = %v, want a refusal naming %s", err, left) } if st, _ := os.Stat(left); st.Mode().Perm() != 0o600 { t.Errorf("the credential file was opened to %o", st.Mode().Perm()) } if st, _ := os.Stat(dir); st.Mode().Perm()&0o005 != 0 { t.Errorf("the folder holding it was opened to %o", st.Mode().Perm()) } }