`files.Put()` and `files.PutTombstone()` accept arbitrary `sig.Name` values without validation. This allows `../` paths to resolve outside the state directory. The NixOS module/systemd service would have prevented this from being a real problem, but best to lock it down all the same.
163 lines
4.0 KiB
Go
163 lines
4.0 KiB
Go
package model_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"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 TestValidateName_Valid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
// static file names
|
|
as.NoError(model.ValidateName("foo"))
|
|
as.NoError(model.ValidateName("foo/bar"))
|
|
as.NoError(model.ValidateName("ns/abc123"))
|
|
as.NoError(model.ValidateName("a"))
|
|
as.NoError(model.ValidateName("with_underscore"))
|
|
as.NoError(model.ValidateName("nested/path/file"))
|
|
|
|
// namespace paths (namespace/url_encoded_key)
|
|
keys := test.GenerateKeys(t, 1)
|
|
as.NoError(model.ValidateName("myns/" + keys[0].Public.URLEncoded()))
|
|
}
|
|
|
|
func TestValidateName_Invalid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
input string
|
|
}{
|
|
// empty
|
|
{"empty string", ""},
|
|
|
|
// null bytes (truncate paths at OS syscall level)
|
|
{"null byte mid-string", "foo\x00bar"},
|
|
{"null byte as traversal smuggle", "valid\x00../../etc/passwd"},
|
|
{"leading null byte", "\x00foo"},
|
|
{"trailing null byte", "foo\x00"},
|
|
|
|
// path traversal
|
|
{"parent traversal", "../../etc/passwd"},
|
|
{"mid-path traversal", "foo/../bar"},
|
|
{"bare dotdot", ".."},
|
|
{"trailing dotdot", "foo/.."},
|
|
{"leading dotdot", "../foo"},
|
|
|
|
// dot as path element (fs.ValidPath rejects)
|
|
{"bare dot", "."},
|
|
{"dot element mid-path", "foo/./bar"},
|
|
|
|
// absolute paths
|
|
{"absolute path", "/etc/passwd"},
|
|
|
|
// empty path segments / structural issues (fs.ValidPath rejects)
|
|
{"trailing slash", "foo/"},
|
|
{"leading slash", "/foo"},
|
|
{"double slash", "foo//bar"},
|
|
|
|
// invalid UTF-8
|
|
{"invalid utf8", "foo\xfe\xff"},
|
|
|
|
// control characters and whitespace
|
|
{"space in name", "foo bar"},
|
|
{"newline in name", "foo\nbar"},
|
|
{"tab in name", "foo\tbar"},
|
|
|
|
// characters not in NameRegex and not valid namespace paths
|
|
{"dot extension", "foo.txt"},
|
|
{"hyphen", "foo-bar"},
|
|
{"uppercase", "FOO"},
|
|
{"backslash", "foo\\bar"},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
require.ErrorIs(t, model.ValidateName(tc.input), model.ErrInvalidName)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateNamespaceName_Valid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
as.NoError(model.ValidateNamespaceName("dns"))
|
|
as.NoError(model.ValidateNamespaceName("my_namespace"))
|
|
as.NoError(model.ValidateNamespaceName("ns123"))
|
|
}
|
|
|
|
func TestValidateNamespaceName_Invalid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
as.Error(model.ValidateNamespaceName("DNS")) // uppercase
|
|
as.Error(model.ValidateNamespaceName("my-ns")) // hyphen
|
|
as.Error(model.ValidateNamespaceName("a/b")) // path separator
|
|
as.Error(model.ValidateNamespaceName("")) // empty
|
|
as.Error(model.ValidateNamespaceName("has space")) // space
|
|
}
|
|
|
|
func TestParseNamespacePath_Valid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
keys := test.GenerateKeys(t, 1)
|
|
encoded := keys[0].Public.URLEncoded()
|
|
name := "myns/" + encoded
|
|
|
|
namespace, signerKey, err := model.ParseNamespacePath(name)
|
|
as.NoError(err)
|
|
as.Equal("myns", namespace)
|
|
as.True(signerKey.Equal(keys[0].Public))
|
|
}
|
|
|
|
func TestParseNamespacePath_NoSlash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
_, _, err := model.ParseNamespacePath("noslash")
|
|
as.Error(err)
|
|
as.Contains(err.Error(), "not a namespace path")
|
|
}
|
|
|
|
func TestParseNamespacePath_InvalidKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
_, _, err := model.ParseNamespacePath("ns/notavalidkey")
|
|
as.Error(err)
|
|
as.Contains(err.Error(), "invalid signer key")
|
|
}
|
|
|
|
func TestParseNamespacePath_RoundTrip(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
as := require.New(t)
|
|
|
|
// generate several keys and verify round-trip through URL encoding
|
|
keys := test.GenerateKeys(t, 5)
|
|
for _, key := range keys {
|
|
name := "test_ns/" + key.Public.URLEncoded()
|
|
_, parsed, err := model.ParseNamespacePath(name)
|
|
as.NoError(err)
|
|
as.True(parsed.Equal(key.Public))
|
|
|
|
// also verify the parsed key can be used with crypto functions
|
|
_, err = crypto.ParsePublicKeyURLEncoded(key.Public.URLEncoded())
|
|
as.NoError(err)
|
|
}
|
|
}
|