133 lines
3.5 KiB
Go
133 lines
3.5 KiB
Go
package shared
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.unkin.net/unkin/golib/keyring"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// TestMain seals tokens with a run-unique key, skipping outside CI where
|
|
// keyctl is unimplemented.
|
|
func TestMain(m *testing.M) {
|
|
KeyringDesc = fmt.Sprintf("vault-tools-test:shared:%d", os.Getpid())
|
|
k, err := keyring.Open(KeyringDesc)
|
|
if errors.Is(err, unix.ENOSYS) && os.Getenv("CI") == "" {
|
|
fmt.Println("skipping: kernel keyring unavailable:", err)
|
|
os.Exit(0)
|
|
}
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
code := m.Run()
|
|
_ = k.Forget()
|
|
os.Exit(code)
|
|
}
|
|
|
|
func writeRaw(t *testing.T, context string, data []byte) string {
|
|
t.Helper()
|
|
path, err := TokenPath(context)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, data, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func TestSaveTokenSealsAtRest(t *testing.T) {
|
|
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
|
if err := SaveToken(&Token{Context: "sydney", Token: "hvs.secret"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path, _ := TokenPath("sydney")
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bytes.Contains(data, []byte("hvs.secret")) {
|
|
t.Fatal("token stored in plaintext")
|
|
}
|
|
entries, _ := os.ReadDir(filepath.Dir(path))
|
|
if len(entries) != 1 {
|
|
t.Errorf("cache dir has %d entries, want only the token file", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestLoadTokenLockedAfterKeyLoss(t *testing.T) {
|
|
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
|
if err := SaveToken(&Token{Context: "sydney", Token: "s.x"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
k, err := keyring.Open(KeyringDesc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := k.Forget(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = LoadToken("sydney")
|
|
if !errors.Is(err, ErrLocked) || !strings.Contains(err.Error(), "vcm login sydney") {
|
|
t.Fatalf("LoadToken = %v, want ErrLocked with login hint", err)
|
|
}
|
|
if path, _ := TokenPath("sydney"); !fileExists(path) {
|
|
t.Error("locked token file was deleted")
|
|
}
|
|
}
|
|
|
|
func TestLoadTokenLegacyPlaintextIsLocked(t *testing.T) {
|
|
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
|
if err := os.MkdirAll(CacheDir(), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := writeRaw(t, "sydney", []byte(`{"context":"sydney","token":"s.x"}`+"\n"))
|
|
if _, err := LoadToken("sydney"); !errors.Is(err, ErrLocked) {
|
|
t.Fatalf("LoadToken = %v, want ErrLocked", err)
|
|
}
|
|
if !fileExists(path) {
|
|
t.Error("legacy token file was deleted")
|
|
}
|
|
}
|
|
|
|
func TestLoadTokenCorruptNamesFile(t *testing.T) {
|
|
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
|
if err := SaveToken(&Token{Context: "sydney", Token: "s.x"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path, _ := TokenPath("sydney")
|
|
data, _ := os.ReadFile(path)
|
|
data[len(data)-1] ^= 0xff
|
|
writeRaw(t, "sydney", data)
|
|
_, err := LoadToken("sydney")
|
|
if !errors.Is(err, keyring.ErrCorrupt) || !strings.Contains(err.Error(), path) {
|
|
t.Fatalf("LoadToken = %v, want ErrCorrupt naming %s", err, path)
|
|
}
|
|
}
|
|
|
|
func fileExists(path string) bool {
|
|
_, err := os.Stat(path)
|
|
return err == nil
|
|
}
|
|
|
|
func TestSaveTokenRenameFailureLeavesNoTemp(t *testing.T) {
|
|
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
|
path, _ := TokenPath("sydney")
|
|
if err := os.MkdirAll(filepath.Join(path, "blocker"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := SaveToken(&Token{Context: "sydney", Token: "s.x"}); err == nil {
|
|
t.Fatal("expected rename onto a non-empty directory to fail")
|
|
}
|
|
if entries, _ := os.ReadDir(CacheDir()); len(entries) != 1 {
|
|
t.Errorf("cache dir has %d entries, want temp file cleaned up", len(entries))
|
|
}
|
|
}
|