194 lines
5.7 KiB
Go
194 lines
5.7 KiB
Go
package store
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func testKey(b byte) []byte { return bytes.Repeat([]byte{b}, 32) }
|
|
|
|
func TestPasswordsEncryptedAndKeyChecked(t *testing.T) {
|
|
ctx := context.Background()
|
|
path := filepath.Join(t.TempDir(), "t.db")
|
|
st, err := Open(path, testKey(1))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SaveCluster(ctx, "", Cluster{Name: "ref", Role: ClusterPrimary, URL: "https://ref:8006", Password: "s3cr3t-root"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var blob []byte
|
|
if err := st.db.QueryRowContext(ctx, `SELECT password FROM clusters WHERE name = 'ref'`).Scan(&blob); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if bytes.Contains(blob, []byte("s3cr3t")) {
|
|
t.Fatal("password stored in clear")
|
|
}
|
|
cls, _ := st.Clusters(ctx)
|
|
if len(cls) != 1 || cls[0].Password != "" || !cls[0].HasPassword || cls[0].User != "root@pam" || cls[0].TLS != TLSPin {
|
|
t.Fatalf("listing must hide the password: %+v", cls)
|
|
}
|
|
// an empty password on update keeps the stored one
|
|
c := cls[0]
|
|
c.Timeout = "20s"
|
|
if err := st.SaveCluster(ctx, "ref", c); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := st.Config(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Reference.Password != "s3cr3t-root" || cfg.Reference.User != "root@pam" {
|
|
t.Fatalf("password after update: %+v", cfg.Reference)
|
|
}
|
|
st.Close()
|
|
if _, err := Open(path, testKey(2)); err == nil {
|
|
t.Fatal("a wrong key must be refused")
|
|
}
|
|
}
|
|
|
|
func TestRolesAndPins(t *testing.T) {
|
|
ctx := context.Background()
|
|
st, err := Open(filepath.Join(t.TempDir(), "t.db"), testKey(1))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
if _, err := st.Config(ctx); err != ErrNoReference {
|
|
t.Fatalf("empty database: %v", err)
|
|
}
|
|
for _, n := range []string{"a", "b", "c"} {
|
|
if err := st.SaveCluster(ctx, "", Cluster{Name: n, URL: "https://" + n + ":8006", Password: "pw"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := st.SetRole(ctx, "a", ClusterPrimary); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SetRole(ctx, "c", ClusterDisabled); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.SetRole(ctx, "b", ClusterPrimary); err != nil { // a becomes a target
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := st.Config(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Reference.Name != "b" || len(cfg.Clients) != 1 || cfg.Clients[0].Name != "a" {
|
|
t.Fatalf("primary %s, targets %+v", cfg.Reference.Name, cfg.Clients)
|
|
}
|
|
if err := st.Pin(ctx, "a", "AA:BB", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", &Check{OK: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, cc, _ := st.Cluster(ctx, "a")
|
|
if cc.Fingerprint != "AA:BB" || cc.SSHHostKey == "" {
|
|
t.Fatalf("pins = %+v", cc)
|
|
}
|
|
// another address: the pins are forgotten
|
|
c, _, _ := st.Cluster(ctx, "a")
|
|
c.URL = "https://a2:8006"
|
|
if err := st.SaveCluster(ctx, "a", c); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, cc, _ = st.Cluster(ctx, "a")
|
|
if cc.Fingerprint != "" || cc.SSHHostKey != "" {
|
|
t.Fatalf("pins kept after an address change: %+v", cc)
|
|
}
|
|
}
|
|
|
|
func TestFirstAdmin(t *testing.T) {
|
|
ctx := context.Background()
|
|
st, err := Open(filepath.Join(t.TempDir(), "t.db"), testKey(1))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
if err := st.CreateFirstAdmin(ctx, "admin", "too-short"); err == nil {
|
|
t.Fatal("short password accepted")
|
|
}
|
|
if err := st.CreateFirstAdmin(ctx, "admin", "a-long-password"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.CreateFirstAdmin(ctx, "other", "a-long-password"); err != ErrSetupDone {
|
|
t.Fatalf("second setup: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUsers(t *testing.T) {
|
|
ctx := context.Background()
|
|
st, err := Open(filepath.Join(t.TempDir(), "t.db"), testKey(1))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
if err := st.SaveUser(ctx, "admin", RoleAdmin, "short"); err == nil {
|
|
t.Fatal("short password accepted")
|
|
}
|
|
if err := st.SaveUser(ctx, "admin", RoleAdmin, "long-enough"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := st.Authenticate(ctx, "admin", "wrong-pass"); ok {
|
|
t.Fatal("wrong password accepted")
|
|
}
|
|
u, ok := st.Authenticate(ctx, "admin", "long-enough")
|
|
if !ok || u.Role != RoleAdmin {
|
|
t.Fatalf("login: %+v %v", u, ok)
|
|
}
|
|
if err := st.SaveUser(ctx, "admin", RoleViewer, ""); err == nil {
|
|
t.Fatal("last admin demoted")
|
|
}
|
|
if err := st.DeleteUser(ctx, "admin"); err == nil {
|
|
t.Fatal("last admin deleted")
|
|
}
|
|
// a new password changes the epoch (open sessions are invalidated)
|
|
if err := st.SaveUser(ctx, "admin", RoleAdmin, "another-one"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
u2, _ := st.User(ctx, "admin")
|
|
if u2.Epoch == u.Epoch {
|
|
t.Fatal("epoch unchanged after a password change")
|
|
}
|
|
}
|
|
|
|
func TestPasswordHash(t *testing.T) {
|
|
h, err := HashPassword("p@ssw0rd!")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(h, "p@ssw0rd") || !CheckPassword(h, "p@ssw0rd!") || CheckPassword(h, "p@ssw0rd") {
|
|
t.Fatal("hash check")
|
|
}
|
|
}
|
|
|
|
func TestMigrateFromTokens(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "t.db")
|
|
db, err := sql.Open("sqlite", "file:"+path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := db.Exec(`CREATE TABLE clusters (name TEXT PRIMARY KEY, reference INTEGER, url TEXT, token_id TEXT, secret BLOB, updated_at INTEGER);
|
|
INSERT INTO clusters VALUES ('old', 1, 'https://x:8006', 'a@pve!t', x'01', 0);
|
|
CREATE TABLE users (name TEXT PRIMARY KEY, role TEXT NOT NULL, password TEXT NOT NULL, epoch INTEGER NOT NULL, created_at INTEGER NOT NULL, last_login INTEGER NOT NULL DEFAULT 0);
|
|
INSERT INTO users VALUES ('admin', 'admin', 'h', 1, 1, 0)`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
db.Close()
|
|
st, err := Open(path, testKey(1))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
cls, err := st.Clusters(context.Background())
|
|
if err != nil || len(cls) != 0 {
|
|
t.Fatalf("clusters after migration: %v %v", cls, err)
|
|
}
|
|
if n, _ := st.CountUsers(context.Background()); n != 1 {
|
|
t.Fatal("users must be kept")
|
|
}
|
|
}
|