486 lines
13 KiB
Go
486 lines
13 KiB
Go
package config_test
|
|
|
|
import (
|
|
"path"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.clan.lol/clan/data-mesher/pkg/config"
|
|
"git.clan.lol/clan/data-mesher/pkg/crypto"
|
|
"git.clan.lol/clan/data-mesher/pkg/model"
|
|
"git.clan.lol/clan/data-mesher/test"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newSignatureAuth(t *testing.T, network config.Network, extra config.ExtraNetworks) *config.SignatureAuth {
|
|
t.Helper()
|
|
|
|
cfg := &config.Config{
|
|
Network: network,
|
|
ExtraNetworks: extra,
|
|
}
|
|
|
|
return config.NewSignatureAuth(cfg)
|
|
}
|
|
|
|
func sig(t *testing.T, networkID *crypto.PublicKey, name string, signedBy *crypto.PublicKey) *model.Signature {
|
|
t.Helper()
|
|
|
|
return &model.Signature{
|
|
NetworkID: networkID,
|
|
Name: name,
|
|
SignedBy: signedBy,
|
|
}
|
|
}
|
|
|
|
func TestSignatureAuth_AuthorizedSigner(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"file_a": {signerKey.Public},
|
|
},
|
|
}, nil)
|
|
|
|
err := auth.Authorize(sig(t, networkKey.Public, "file_a", signerKey.Public))
|
|
as.NoError(err)
|
|
}
|
|
|
|
func TestSignatureAuth_NetworkIsImplicitSigner(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"file_a": {signerKey.Public},
|
|
},
|
|
}, nil)
|
|
|
|
// The network key itself should be an implicit signer for all files
|
|
err := auth.Authorize(sig(t, networkKey.Public, "file_a", networkKey.Public))
|
|
as.NoError(err)
|
|
}
|
|
|
|
func TestSignatureAuth_UnauthorizedSigner(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 3)
|
|
keys, networkKey := allKeys[:2], allKeys[2]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"file_a": {keys[0].Public},
|
|
},
|
|
}, nil)
|
|
|
|
// keys[1] is not authorized for file_a
|
|
err := auth.Authorize(sig(t, networkKey.Public, "file_a", keys[1].Public))
|
|
as.ErrorIs(err, config.ErrUnauthorizedSigner)
|
|
}
|
|
|
|
func TestSignatureAuth_UnconfiguredFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"file_a": {signerKey.Public},
|
|
},
|
|
}, nil)
|
|
|
|
err := auth.Authorize(sig(t, networkKey.Public, "unknown_file", signerKey.Public))
|
|
as.ErrorIs(err, config.ErrUnauthorizedFile)
|
|
}
|
|
|
|
func TestSignatureAuth_UnknownNetwork(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 3)
|
|
signerKey, networkKey, unknownNetwork := allKeys[0], allKeys[1], allKeys[2]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"file_a": {signerKey.Public},
|
|
},
|
|
}, nil)
|
|
|
|
err := auth.Authorize(sig(t, unknownNetwork.Public, "file_a", signerKey.Public))
|
|
as.ErrorIs(err, config.ErrUnauthorizedNetwork)
|
|
}
|
|
|
|
func TestSignatureAuth_ExtraNetwork(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 4)
|
|
keys, homeNetwork, extraNetwork := allKeys[:2], allKeys[2], allKeys[3]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: homeNetwork.Public,
|
|
Files: config.Files{
|
|
"home_file": {keys[0].Public},
|
|
},
|
|
}, config.ExtraNetworks{
|
|
"other": {
|
|
ID: extraNetwork.Public,
|
|
Files: config.Files{
|
|
"extra_file": {keys[1].Public},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Home network signer authorized for home file
|
|
as.NoError(auth.Authorize(sig(t, homeNetwork.Public, "home_file", keys[0].Public)))
|
|
|
|
// Extra network signer authorized for extra file
|
|
as.NoError(auth.Authorize(sig(t, extraNetwork.Public, "extra_file", keys[1].Public)))
|
|
|
|
// Extra network key is implicit signer for its own files
|
|
as.NoError(auth.Authorize(sig(t, extraNetwork.Public, "extra_file", extraNetwork.Public)))
|
|
|
|
// Home signer not authorized for extra network file
|
|
as.ErrorIs(
|
|
auth.Authorize(sig(t, extraNetwork.Public, "extra_file", keys[0].Public)),
|
|
config.ErrUnauthorizedSigner,
|
|
)
|
|
|
|
// Extra signer not authorized for home network file
|
|
as.ErrorIs(
|
|
auth.Authorize(sig(t, homeNetwork.Public, "home_file", keys[1].Public)),
|
|
config.ErrUnauthorizedSigner,
|
|
)
|
|
|
|
// Home network key is not an implicit signer for extra network files
|
|
as.ErrorIs(
|
|
auth.Authorize(sig(t, extraNetwork.Public, "extra_file", homeNetwork.Public)),
|
|
config.ErrUnauthorizedSigner,
|
|
)
|
|
}
|
|
|
|
func TestSignatureAuth_MultipleSignersPerFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 4)
|
|
keys, networkKey := allKeys[:3], allKeys[3]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"shared": {keys[0].Public, keys[1].Public},
|
|
},
|
|
}, nil)
|
|
|
|
as.NoError(auth.Authorize(sig(t, networkKey.Public, "shared", keys[0].Public)))
|
|
as.NoError(auth.Authorize(sig(t, networkKey.Public, "shared", keys[1].Public)))
|
|
|
|
as.ErrorIs(
|
|
auth.Authorize(sig(t, networkKey.Public, "shared", keys[2].Public)),
|
|
config.ErrUnauthorizedSigner,
|
|
)
|
|
}
|
|
|
|
func TestSignatureAuth_NoFiles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
}, nil)
|
|
|
|
err := auth.Authorize(sig(t, networkKey.Public, "anything", signerKey.Public))
|
|
as.ErrorIs(err, config.ErrUnauthorizedFile)
|
|
}
|
|
|
|
// namespaceSig creates a signature for a namespace path with an optional certificate.
|
|
func namespaceSig(
|
|
t *testing.T,
|
|
networkKey *crypto.PrivateKey,
|
|
signerKey *crypto.PrivateKey,
|
|
namespace string,
|
|
cert *crypto.Certificate,
|
|
signedAt time.Time,
|
|
) *model.Signature {
|
|
t.Helper()
|
|
|
|
name := path.Join(namespace, signerKey.Public.URLEncoded())
|
|
|
|
return &model.Signature{
|
|
NetworkID: networkKey.Public,
|
|
Name: name,
|
|
SignedBy: signerKey.Public,
|
|
Certificate: cert,
|
|
SignedAt: signedAt,
|
|
}
|
|
}
|
|
|
|
func TestSignatureAuth_NamespaceAuthorized(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
now := time.Now()
|
|
cert, err := crypto.SignCertificate("peer1", signerKey.Public, networkKey, now.Add(-time.Hour), now.Add(time.Hour))
|
|
as.NoError(err)
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Namespaces: []string{"dns"},
|
|
Files: config.Files{"placeholder": {signerKey.Public}},
|
|
}, nil)
|
|
|
|
err = auth.Authorize(namespaceSig(t, networkKey, signerKey, "dns", cert, now))
|
|
as.NoError(err)
|
|
}
|
|
|
|
func TestSignatureAuth_NamespaceRequiresCertificate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Namespaces: []string{"dns"},
|
|
Files: config.Files{"placeholder": {signerKey.Public}},
|
|
}, nil)
|
|
|
|
// no certificate provided
|
|
err := auth.Authorize(namespaceSig(t, networkKey, signerKey, "dns", nil, time.Now()))
|
|
as.ErrorIs(err, config.ErrUnauthorizedSigner)
|
|
as.ErrorContains(err, "certificate required")
|
|
}
|
|
|
|
func TestSignatureAuth_NamespaceKeyMismatch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 3)
|
|
signerKeys, networkKey := allKeys[:2], allKeys[2]
|
|
|
|
now := time.Now()
|
|
cert, err := crypto.SignCertificate("peer1", signerKeys[0].Public, networkKey, now.Add(-time.Hour), now.Add(time.Hour))
|
|
as.NoError(err)
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Namespaces: []string{"dns"},
|
|
Files: config.Files{"placeholder": {signerKeys[0].Public}},
|
|
}, nil)
|
|
|
|
// build a signature where the URL path has signerKeys[0] but SignedBy is signerKeys[1]
|
|
s := namespaceSig(t, networkKey, signerKeys[0], "dns", cert, now)
|
|
s.SignedBy = signerKeys[1].Public
|
|
|
|
err = auth.Authorize(s)
|
|
as.ErrorIs(err, config.ErrUnauthorizedSigner)
|
|
as.ErrorContains(err, "signer key does not match namespace path")
|
|
}
|
|
|
|
func TestSignatureAuth_NamespaceCertNotSignedByNetwork(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 3)
|
|
signerKey, networkKey, otherNetworkKey := allKeys[0], allKeys[1], allKeys[2]
|
|
|
|
now := time.Now()
|
|
// certificate signed by a different network key
|
|
cert, err := crypto.SignCertificate(
|
|
"peer1", signerKey.Public, otherNetworkKey, now.Add(-time.Hour), now.Add(time.Hour),
|
|
)
|
|
as.NoError(err)
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Namespaces: []string{"dns"},
|
|
Files: config.Files{"placeholder": {signerKey.Public}},
|
|
}, nil)
|
|
|
|
err = auth.Authorize(namespaceSig(t, networkKey, signerKey, "dns", cert, now))
|
|
as.ErrorIs(err, config.ErrUnauthorizedSigner)
|
|
as.ErrorContains(err, "certificate verification failed")
|
|
}
|
|
|
|
func TestSignatureAuth_NamespaceNotConfigured(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
now := time.Now()
|
|
cert, err := crypto.SignCertificate("peer1", signerKey.Public, networkKey, now.Add(-time.Hour), now.Add(time.Hour))
|
|
as.NoError(err)
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Namespaces: []string{"dns"},
|
|
Files: config.Files{"placeholder": {signerKey.Public}},
|
|
}, nil)
|
|
|
|
// "metrics" is not a configured namespace
|
|
err = auth.Authorize(namespaceSig(t, networkKey, signerKey, "metrics", cert, now))
|
|
as.ErrorIs(err, config.ErrUnauthorizedFile)
|
|
}
|
|
|
|
func TestSignatureAuth_NamespaceExpiredCertificate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
now := time.Now()
|
|
// certificate valid from 2 hours ago to 1 hour ago (expired)
|
|
cert, err := crypto.SignCertificate("peer1", signerKey.Public, networkKey, now.Add(-2*time.Hour), now.Add(-time.Hour))
|
|
as.NoError(err)
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Namespaces: []string{"dns"},
|
|
Files: config.Files{"placeholder": {signerKey.Public}},
|
|
}, nil)
|
|
|
|
// SignedAt is now, but certificate expired an hour ago — wall-clock check rejects it
|
|
err = auth.Authorize(namespaceSig(t, networkKey, signerKey, "dns", cert, now))
|
|
as.ErrorIs(err, config.ErrUnauthorizedSigner)
|
|
as.ErrorContains(err, "certificate verification failed")
|
|
}
|
|
|
|
func TestSignatureAuth_NamespaceExtraNetwork(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 3)
|
|
signerKey, homeKey, extraKey := allKeys[0], allKeys[1], allKeys[2]
|
|
|
|
now := time.Now()
|
|
// certificate signed by extra network
|
|
cert, err := crypto.SignCertificate("peer1", signerKey.Public, extraKey, now.Add(-time.Hour), now.Add(time.Hour))
|
|
as.NoError(err)
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: homeKey.Public,
|
|
Files: config.Files{"home_file": {signerKey.Public}},
|
|
}, config.ExtraNetworks{
|
|
"extra": {
|
|
ID: extraKey.Public,
|
|
Namespaces: []string{"shared"},
|
|
Files: config.Files{"extra_file": {signerKey.Public}},
|
|
},
|
|
})
|
|
|
|
// namespace on extra network should work
|
|
err = auth.Authorize(namespaceSig(t, extraKey, signerKey, "shared", cert, now))
|
|
as.NoError(err)
|
|
|
|
// same namespace name on home network should fail (not configured there)
|
|
homeCert, err := crypto.SignCertificate("peer1", signerKey.Public, homeKey, now.Add(-time.Hour), now.Add(time.Hour))
|
|
as.NoError(err)
|
|
|
|
err = auth.Authorize(namespaceSig(t, homeKey, signerKey, "shared", homeCert, now))
|
|
as.ErrorIs(err, config.ErrUnauthorizedFile)
|
|
}
|
|
|
|
func TestSignatureAuth_StaticAndNamespaceBothWork(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
allKeys := test.GenerateKeys(t, 2)
|
|
signerKey, networkKey := allKeys[0], allKeys[1]
|
|
|
|
now := time.Now()
|
|
cert, err := crypto.SignCertificate("peer1", signerKey.Public, networkKey, now.Add(-time.Hour), now.Add(time.Hour))
|
|
as.NoError(err)
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{"static_file": {signerKey.Public}},
|
|
Namespaces: []string{"dynamic"},
|
|
}, nil)
|
|
|
|
// static file authorization works
|
|
as.NoError(auth.Authorize(sig(t, networkKey.Public, "static_file", signerKey.Public)))
|
|
|
|
// namespace authorization works
|
|
as.NoError(auth.Authorize(namespaceSig(t, networkKey, signerKey, "dynamic", cert, now)))
|
|
}
|
|
|
|
func TestSignatureAuth_NilNetworkID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
keys := test.GenerateKeys(t, 2)
|
|
networkKey, signerKey := keys[0], keys[1]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"file_a": {signerKey.Public},
|
|
},
|
|
}, nil)
|
|
|
|
err := auth.Authorize(sig(t, nil, "file_a", signerKey.Public))
|
|
as.ErrorIs(err, config.ErrUnauthorizedSigner)
|
|
}
|
|
|
|
func TestSignatureAuth_NilSignedBy(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
keys := test.GenerateKeys(t, 2)
|
|
networkKey := keys[0]
|
|
|
|
auth := newSignatureAuth(t, config.Network{
|
|
ID: networkKey.Public,
|
|
Files: config.Files{
|
|
"file_a": nil,
|
|
},
|
|
}, nil)
|
|
|
|
err := auth.Authorize(sig(t, networkKey.Public, "file_a", nil))
|
|
as.ErrorIs(err, config.ErrUnauthorizedSigner)
|
|
}
|