Golang key management API server and library, based on SecuritySuite.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

745 lines
19 KiB

package keystar_test
import (
"net/http/httptest"
"net/url"
"testing"
"time"
"git.destrealm.org/go/errors"
"git.destrealm.org/go/keystar"
"git.destrealm.org/go/keystar/client"
. "git.destrealm.org/go/keystar/errors"
"git.destrealm.org/go/keystar/server"
kt "git.destrealm.org/go/keystar/types"
"git.destrealm.org/go/keystar/types/api"
"git.destrealm.org/go/keystar/types/helpers"
)
func clientTest(ns *api.Namespace, keyringName string, t *testing.T) {
keyring, err := ns.CreateKeyRing(keyringName, 300)
if err != nil {
t.Error("error creating key ring:", errors.Unfurl(err))
return
}
if keyring.Name != keyringName {
t.Errorf(`expected key ring name of "%s" but got "%s"`,
keyringName, keyring.Name)
}
if keyring.TTL != time.Second*300 {
t.Errorf(`expected key ring TTL of 300s but got %d`, keyring.TTL)
}
if !keyring.Created.After(time.Now().Add(-time.Duration(time.Second * 1))) {
t.Errorf(`expected creation time within 5 seconds resolution, got %v`,
keyring.Created)
}
// Create test key within our key ring.
key1, err := keyring.Create("test-key", 32)
if err != nil {
t.Error("error creating key:", errors.Unfurl(err))
return
}
if key1.Name != "test-key" {
t.Errorf(`expected key name of "test-key" but got "%s"`, key1.Name)
}
if key1.Length != 32 {
t.Errorf(`expected key length of 32 but got %d`, key1.Length)
}
if key1.KeyRing().Name != keyring.Name {
t.Error("key ring name did not match the key ring assigned to key")
}
// Validate key ring contents using keyring.Get() (uses .Read() internally if
// the key hasn't been cached).
key2, err := keyring.Get("test-key")
if err != nil {
t.Error("error calling Get() on key:", err)
return
}
if key2.Name != "test-key" {
t.Errorf(`expected key name of "test-key" but got "%s"`, key2.Name)
}
if key2.Length != 32 {
t.Errorf(`expected key length of 32 but got %d`, key2.Length)
}
// Validate key by forcibly bypassing the keyring's internal cache.
key3, err := keyring.Read("test-key")
if err != nil {
t.Error("error calling Read() on key:", errors.Unfurl(err))
return
}
if key3.Name != "test-key" {
t.Errorf(`expected key name of "test-key" but got "%s"`, key3.Name)
}
if key3.Length != 32 {
t.Errorf(`expected key length of 32 but got %d`, key3.Length)
}
// Validate cache by calling .Get() again.
key4, err := keyring.Get("test-key")
if err != nil {
t.Error("error creating key:", err)
return
}
if key4.Name != "test-key" {
t.Errorf(`expected key name of "test-key" but got "%s"`, key4.Name)
}
if key4.Length != 32 {
t.Errorf(`expected key length of 32 but got %d`, key4.Length)
}
// Verify all encoded data is the same.
if key2.Encoded != key1.Encoded {
t.Error(`key data from first .Get() did not match original`)
}
if key3.Encoded != key1.Encoded {
t.Error(`key data from .Read() did not match original`)
}
if key4.Encoded != key1.Encoded {
t.Error(`key data from second .Get() did not match original`)
}
encoded := key1.Encoded
if err = keyring.Rotate(); err != nil {
t.Error("error rotating key ring:", err)
}
key2, _ = keyring.Get("test-key")
key3, _ = keyring.Read("test-key")
if key2.Encoded == encoded {
t.Error("encoded keys should change after rotate")
}
if key3.Encoded == encoded {
t.Error("encoded keys should change after rotate")
}
if key2.Version != key3.Version && key2.Version != 1 {
t.Error("key version should be incremented")
}
err = keyring.Remove(key1.Name)
if err != nil {
t.Error("could not remove key:", err)
return
}
_, err = keyring.Get("test-key")
if err == nil {
t.Error("error should be returned (got nil)")
} else if !errors.Guarantee(err).Is(ErrNoSuchKey) {
t.Error("expected ErrNoSuchKey, got:", err)
}
comps, _ := ns.CreateKeyRing("test-composite-key-fixture", 0)
ck, err := comps.CreateComposite("test-composite-key", 32, 128)
if err != nil {
t.Error("error creating composite key:", err)
}
if ck.Cipher.Length != 32 {
t.Errorf(`composite key cipher length should be 32, got %d`, ck.Cipher.Length)
}
if ck.HMAC.Length != 128 {
t.Errorf(`composite key HMAC length should be 128, got %d`, ck.HMAC.Length)
}
ckfs, err := api.CompositeKeyFromKeyRing("test-composite-key", comps)
if err != nil {
t.Error("error loading composite key from provided key ring:", err)
return
}
ckkr, err := comps.GetComposite("test-composite-key")
if err != nil {
t.Error("error loading composite key from key ring:", err)
return
}
if ckfs.Cipher.Length != 32 {
t.Errorf(`composite key cipher length should be 32, got %d`, ck.Cipher.Length)
}
if ckfs.HMAC.Length != 128 {
t.Errorf(`composite key HMAC length should be 128, got %d`, ck.HMAC.Length)
}
if ckfs.Cipher.Length != ckkr.Cipher.Length {
t.Errorf(`composite key cipher length should be 32, got %d`, ck.Cipher.Length)
}
if ckfs.HMAC.Length != ckkr.HMAC.Length {
t.Errorf(`composite key HMAC length should be 128, got %d`, ck.HMAC.Length)
}
if ck.Cipher.Encoded != ckfs.Cipher.Encoded {
t.Error("encoded cipher keys for composite key don't match")
}
if ck.HMAC.Encoded != ckfs.HMAC.Encoded {
t.Error("encoded HMAC keys for composite key don't match")
}
if ckkr.Cipher.Encoded != ckfs.Cipher.Encoded {
t.Error("encoded cipher keys for composite key don't match")
}
if ckkr.HMAC.Encoded != ckfs.HMAC.Encoded {
t.Error("encoded HMAC keys for composite key don't match")
}
// Test key ring update.
keyring.TTL = 500 * time.Second
err = ns.UpdateKeyRing(keyring)
if err != nil {
t.Error("could not update key ring:", err)
}
keyring, err = ns.GetKeyRing(keyring.Name)
if err != nil {
t.Error("could not retrieve key ring:", err)
}
if keyring.TTL != 500*time.Second {
t.Errorf(`keyring TTL was not updated; got %d, expected 500`,
int(keyring.TTL/time.Second))
}
keyring, err = ns.GetOrCreateKeyRing("test-list-keys", 0)
if err != nil {
t.Error("unable to create or retrieve key ring:", err)
return
}
if _, err = keyring.Create("test-1", 32); err != nil {
t.Error("unable to create key:", err)
return
}
if _, err = keyring.Create("test-2", 32); err != nil {
t.Error("unable to create key:", err)
return
}
if _, err = keyring.Create("test-3", 32); err != nil {
t.Error("unable to create key:", err)
return
}
if keys, err := keyring.Mapped(); err != nil {
t.Error("unable to list contents of key ring:", errors.Unfurl(err))
return
} else {
if _, ok := keys["test-1"]; !ok {
t.Error(`"test-1" key does not exist`)
}
if _, ok := keys["test-2"]; !ok {
t.Error(`"test-2" key does not exist`)
}
if _, ok := keys["test-3"]; !ok {
t.Error(`"test-3" key does not exist`)
}
}
}
func Test_KeyStar_Client_TestBackend(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
clientTest(ks.KeySpace().Global(), "test-keyring-create", t)
}
func Test_KeyStar_Client_TestBackend_Namespaces(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing")
if err != nil {
t.Error(`could not switch to namespace "testing":`, err)
return
}
clientTest(ns, "test-keyring-ns", t)
}
func Test_KeyStar_Client_FSNamespace(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "file://testdata/fs",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
clientTest(ks.KeySpace().Global(), "fs-keyring-create", t)
}
func Test_KeyStar_Client_FSNamespace_Namespaces(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "file://testdata/fs",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing")
if err != nil {
t.Error(`could not switch to namespace "testing":`, err)
return
}
clientTest(ns, "fs-keyring-ns", t)
}
func Test_KeyStar_Client_FSNamespaceNoHash(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "file://testdata/fs?hash=test",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
clientTest(ks.KeySpace().Global(), "fs-keyring-nohash-create", t)
}
func Test_KeyStar_Client_FSNamespaceNoHash_Namespaces(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "file://testdata/fs?hash=test",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing")
if err != nil {
t.Error(`could not switch to namespace "testing":`, err)
return
}
clientTest(ns, "fs-keyring-nohash-ns", t)
}
func Test_KeyStar_Client_HTTPNamespace(t *testing.T) {
var ts *httptest.Server
lks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("error initializing KeyStar server instance for HTTP client:", err)
return
}
id, secret, err := helpers.Initialize(lks)
if err != nil {
t.Error("error initializing keyspace:", err)
return
}
server := server.NewServer(&server.Config{})
server.SetKeyStar(lks)
ts = httptest.NewServer(server.Handler())
defer ts.Close()
u, _ := url.Parse(ts.URL)
values := url.Values{}
values.Add("id", id.Encoded)
values.Add("secret", secret.Encoded)
u.RawQuery = values.Encode()
// Client KeyStar instance.
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: u.String(),
Initializer: func(opts *kt.Options) (kt.NamespaceInitializer, error) {
return func(ns *api.Namespace) (api.NamespaceHandler, error) {
backend, err := client.NewHTTPNamespace(opts, ts.Client())
if err != nil {
return nil, err
}
if err := backend.Init(ns); err != nil {
return nil, err
}
return backend, nil
}, nil
},
})
if err != nil {
t.Error("could not create KeyStar client:", errors.Unfurl(err))
return
}
clientTest(ks.KeySpace().Global(), "http-keyring-create", t)
}
func Test_KeyStar_Client_HTTPNamespace_Namespaces(t *testing.T) {
var ts *httptest.Server
lks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("error initializing KeyStar server instance for HTTP client:", err)
return
}
id, secret, err := helpers.Initialize(lks)
if err != nil {
t.Error("error initializing keyspace:", err)
return
}
server := server.NewServer(&server.Config{})
server.SetKeyStar(lks)
ts = httptest.NewServer(server.Handler())
defer ts.Close()
u, _ := url.Parse(ts.URL)
values := url.Values{}
values.Add("id", id.Encoded)
values.Add("secret", secret.Encoded)
u.RawQuery = values.Encode()
// Client KeyStar instance.
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: u.String(),
Initializer: func(opts *kt.Options) (kt.NamespaceInitializer, error) {
return func(ns *api.Namespace) (api.NamespaceHandler, error) {
backend, err := client.NewHTTPNamespace(opts, ts.Client())
if err != nil {
return nil, err
}
if err := backend.Init(ns); err != nil {
return nil, err
}
return backend, nil
}, nil
},
})
if err != nil {
t.Error("could not create KeyStar client:", errors.Unfurl(err))
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing")
if err != nil {
t.Error(`could not switch to namespace "testing":`, err)
return
}
clientTest(ns, "http-keyring-ns", t)
}
func destroyKeyRingTest(ns *api.Namespace, keyringName string, t *testing.T) {
var err error
_, err = ns.CreateKeyRing(keyringName, 0)
if err != nil {
t.Errorf(`could not create key ring "%s": %v`, keyringName, err)
return
}
// Destroy key ring.
if err := ns.DestroyKeyRing(keyringName); err != nil {
t.Errorf(`could not destroy key ring "%s": %v`, keyringName, err)
return
}
// Verify key ring does not exist.
if kr, err := ns.KeyRing(keyringName, false); err != nil {
if !errors.Guarantee(err).Is(ErrNoSuchKeyRing) {
t.Errorf(`expected ErrNoSuchKeyRing, got "%s" instead`, ErrNoSuchKeyRing)
}
} else {
if kr.Name == keyringName {
t.Errorf(`key ring "%s" should have been destroyed, it was not`,
keyringName)
}
}
}
func Test_KeyStar_Client_TestBackend_DestroyKeyRing(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns := ks.KeySpace().Global()
destroyKeyRingTest(ns, "testing-destroy-me", t)
}
func Test_KeyStar_Client_TestBackend_DestroyKeyRing_Namespace(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing-destroy")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy"`, err)
return
}
destroyKeyRingTest(ns, "destroy-me", t)
}
func Test_KeyStar_Client_FSNamespace_DestroyKeyRing(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "file://testdata/fs?hash=test",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns := ks.KeySpace().Global()
destroyKeyRingTest(ns, "testing-destroy-me", t)
}
func Test_KeyStar_Client_FSNamespace_DestroyKeyRing_Namespace(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "file://testdata/fs?hash=test",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing-destroy")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy"`, err)
return
}
destroyKeyRingTest(ns, "destroy-me", t)
}
func Test_KeyStar_Client_HTTPNamespace_DestroyKeyRing(t *testing.T) {
var ts *httptest.Server
lks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("error initializing KeyStar server instance for HTTP client:", err)
return
}
id, secret, err := helpers.Initialize(lks)
if err != nil {
t.Error("error initializing keyspace:", err)
return
}
server := server.NewServer(&server.Config{})
server.SetKeyStar(lks)
ts = httptest.NewServer(server.Handler())
defer ts.Close()
u, _ := url.Parse(ts.URL)
values := url.Values{}
values.Add("id", id.Encoded)
values.Add("secret", secret.Encoded)
u.RawQuery = values.Encode()
// Client KeyStar instance.
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: u.String(),
Initializer: func(opts *kt.Options) (kt.NamespaceInitializer, error) {
return func(ns *api.Namespace) (api.NamespaceHandler, error) {
backend, err := client.NewHTTPNamespace(opts, ts.Client())
if err != nil {
return nil, err
}
if err := backend.Init(ns); err != nil {
return nil, err
}
return backend, nil
}, nil
},
})
if err != nil {
t.Error("could not create KeyStar client:", errors.Unfurl(err))
return
}
ns := ks.KeySpace().Global()
destroyKeyRingTest(ns, "testing-destroy-me", t)
}
func Test_KeyStar_Client_HTTPNamespace_DestroyKeyRing_Namespace(t *testing.T) {
var ts *httptest.Server
lks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("error initializing KeyStar server instance for HTTP client:", err)
return
}
id, secret, err := helpers.Initialize(lks)
if err != nil {
t.Error("error initializing keyspace:", err)
return
}
server := server.NewServer(&server.Config{})
server.SetKeyStar(lks)
ts = httptest.NewServer(server.Handler())
defer ts.Close()
u, _ := url.Parse(ts.URL)
values := url.Values{}
values.Add("id", id.Encoded)
values.Add("secret", secret.Encoded)
u.RawQuery = values.Encode()
// Client KeyStar instance.
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: u.String(),
Initializer: func(opts *kt.Options) (kt.NamespaceInitializer, error) {
return func(ns *api.Namespace) (api.NamespaceHandler, error) {
backend, err := client.NewHTTPNamespace(opts, ts.Client())
if err != nil {
return nil, err
}
if err := backend.Init(ns); err != nil {
return nil, err
}
return backend, nil
}, nil
},
})
if err != nil {
t.Error("could not create KeyStar client:", errors.Unfurl(err))
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing")
if err != nil {
t.Error(`could not switch to namespace "testing":`, err)
return
}
destroyKeyRingTest(ns, "destroy-me", t)
}
func Test_KeyStar_Client_TestBackend_DestroyNamespace_Namespace(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing-destroy-ns")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy-ns"`, err)
return
}
// Destroy namespace.
if err := ns.Destroy(); err != nil {
t.Error(`could not destroy namespace`)
}
// Recreate namespace.
ns, err = ks.KeySpace().GlobalNamespace("testing-destroy-ns")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy-ns"`, err)
return
}
}
func Test_KeyStar_Client_FSNamespace_DestroyNamespace_Namespace(t *testing.T) {
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "file://testdata/fs?hash=test",
})
if err != nil {
t.Error("could not create KeyStar client:", err)
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing-destroy-ns")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy-ns"`, err)
return
}
// Destroy namespace.
if err := ns.Destroy(); err != nil {
t.Error(`could not destroy namespace`)
}
// Recreate namespace.
ns, err = ks.KeySpace().GlobalNamespace("testing-destroy-ns")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy-ns"`, err)
return
}
}
func Test_KeyStar_Client_HTTPNamespace_DestroyNamespace_Namespace(t *testing.T) {
var ts *httptest.Server
lks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: "memory://",
})
if err != nil {
t.Error("error initializing KeyStar server instance for HTTP client:", err)
return
}
id, secret, err := helpers.Initialize(lks)
if err != nil {
t.Error("error initializing keyspace:", err)
return
}
server := server.NewServer(&server.Config{})
server.SetKeyStar(lks)
ts = httptest.NewServer(server.Handler())
defer ts.Close()
u, _ := url.Parse(ts.URL)
values := url.Values{}
values.Add("id", id.Encoded)
values.Add("secret", secret.Encoded)
u.RawQuery = values.Encode()
// Client KeyStar instance.
ks, err := keystar.NewKeyStar(&kt.Options{
BackendURI: u.String(),
Initializer: func(opts *kt.Options) (kt.NamespaceInitializer, error) {
return func(ns *api.Namespace) (api.NamespaceHandler, error) {
backend, err := client.NewHTTPNamespace(opts, ts.Client())
if err != nil {
return nil, err
}
if err := backend.Init(ns); err != nil {
return nil, err
}
return backend, nil
}, nil
},
})
if err != nil {
t.Error("could not create KeyStar client:", errors.Unfurl(err))
return
}
ns, err := ks.KeySpace().GlobalNamespace("testing-destroy-ns")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy-ns"`, err)
return
}
// Destroy namespace.
if err := ns.Destroy(); err != nil {
t.Error(`could not destroy namespace`)
}
// Recreate namespace.
ns, err = ks.KeySpace().GlobalNamespace("testing-destroy-ns")
if err != nil {
t.Error(`could not switch to namespace "testing-destroy-ns"`, err)
return
}
}