244 lines
7.7 KiB
Go
244 lines
7.7 KiB
Go
package authprojection
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewSnapshotUsesDomainSeparatedGeneration(t *testing.T) {
|
|
got, err := NewSnapshot("local", []byte("auth\n"), []byte("users\n"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
auth := sha256.Sum256([]byte("auth\n"))
|
|
users := sha256.Sum256([]byte("users\n"))
|
|
record := fmt.Sprintf("thothii-auth-projection-v1\nmode=local\nauth=%x\nusers=%x\n", auth, users)
|
|
generation := sha256.Sum256([]byte(record))
|
|
if got.Generation != hex.EncodeToString(generation[:]) {
|
|
t.Fatalf("generation = %q", got.Generation)
|
|
}
|
|
if got.CanonicalRevision != "sha256:"+got.Generation {
|
|
t.Fatalf("revision = %q", got.CanonicalRevision)
|
|
}
|
|
if string(got.Auth) != "auth\n" || string(got.Users) != "users\n" {
|
|
t.Fatal("snapshot did not preserve supplied bytes")
|
|
}
|
|
}
|
|
|
|
func TestNewSnapshotRejectsInvalidModeAndUsersShape(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
mode string
|
|
users []byte
|
|
}{
|
|
{mode: "unknown", users: nil},
|
|
{mode: "local", users: nil},
|
|
{mode: "oidc", users: []byte("unexpected")},
|
|
} {
|
|
if _, err := NewSnapshot(tc.mode, []byte("auth"), tc.users); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("NewSnapshot(%q) error = %v", tc.mode, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewSnapshotCopiesAndBoundsInput(t *testing.T) {
|
|
auth := []byte("auth")
|
|
users := []byte("users")
|
|
got, err := NewSnapshot("local", auth, users)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
auth[0] = 'x'
|
|
users[0] = 'x'
|
|
if string(got.Auth) != "auth" || string(got.Users) != "users" {
|
|
t.Fatal("snapshot aliases caller buffers")
|
|
}
|
|
if _, err := NewSnapshot("oidc", make([]byte, maximumAuthBytes+1), nil); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("oversized auth error = %v", err)
|
|
}
|
|
if _, err := NewSnapshot("local", []byte("auth"), make([]byte, maximumUsersBytes+1)); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("oversized users error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSelectorStrictlyDecodesReadyAndBlocked(t *testing.T) {
|
|
tx := strings.Repeat("a", 32)
|
|
gen := strings.Repeat("b", 64)
|
|
previous := strings.Repeat("c", 64)
|
|
for _, tc := range []struct {
|
|
name string
|
|
body string
|
|
want Selector
|
|
}{
|
|
{
|
|
name: "ready",
|
|
body: fmt.Sprintf(`{"version":1,"state":"ready","transaction":"%s","generation":"%s","previousGenerations":["%s"]}`, tx, gen, previous),
|
|
want: Selector{Version: 1, State: "ready", Transaction: tx, Generation: gen, PreviousGenerations: []string{previous}},
|
|
},
|
|
{
|
|
name: "blocked",
|
|
body: fmt.Sprintf(`{"version":1,"state":"blocked","transaction":"%s"}`, tx),
|
|
want: Selector{Version: 1, State: "blocked", Transaction: tx},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := decodeSelector(append([]byte(tc.body), '\n'))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fmt.Sprintf("%#v", got) != fmt.Sprintf("%#v", tc.want) {
|
|
t.Fatalf("selector = %#v, want %#v", got, tc.want)
|
|
}
|
|
encoded, err := encodeSelector(got)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasSuffix(string(encoded), "\n") || strings.Count(string(encoded), "\n") != 1 {
|
|
t.Fatalf("encoded selector is not canonical newline-terminated JSON: %q", encoded)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSelectorRejectsDuplicatesUnknownsAndInvalidIdentifiers(t *testing.T) {
|
|
tx := strings.Repeat("a", 32)
|
|
gen := strings.Repeat("b", 64)
|
|
cases := []string{
|
|
fmt.Sprintf(`{"version":1,"version":1,"state":"blocked","transaction":"%s"}`, tx),
|
|
fmt.Sprintf(`{"version":1,"state":"blocked","transaction":"%s","extra":true}`, tx),
|
|
fmt.Sprintf(`{"version":1,"state":"ready","transaction":"%s","generation":"%s","previousGenerations":["%s","%s"]}`, tx, gen, gen, gen),
|
|
fmt.Sprintf(`{"version":1,"state":"ready","transaction":"%s","generation":"%s","previousGenerations":["%s","%s","%s"]}`, tx, gen, strings.Repeat("c", 64), strings.Repeat("d", 64), strings.Repeat("e", 64)),
|
|
fmt.Sprintf(`{"version":1,"state":"ready","transaction":"%s","generation":"%s"}`, strings.Repeat("A", 32), gen),
|
|
fmt.Sprintf(`{"version":1,"state":"blocked","transaction":"%s","generation":"%s"}`, tx, gen),
|
|
fmt.Sprintf(`{"version":1,"state":"ready","transaction":"%s","generation":"%s"} trailing`, tx, gen),
|
|
}
|
|
for _, body := range cases {
|
|
if _, err := decodeSelector([]byte(body)); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("decodeSelector(%q) error = %v", body, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestManifestStrictlyValidatesExactModeDependentFiles(t *testing.T) {
|
|
snapshot, err := NewSnapshot("local", []byte("auth"), []byte("users"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
manifest, err := newManifest(snapshot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
encoded, err := encodeManifest(manifest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasSuffix(string(encoded), "\n") || strings.Count(string(encoded), "\n") != 1 {
|
|
t.Fatalf("manifest is not canonical newline-terminated JSON: %q", encoded)
|
|
}
|
|
got, err := decodeManifest(encoded)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := validateManifest(got, snapshot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bad := strings.Replace(string(encoded), `"users.yaml"`, `"other.yaml"`, 1)
|
|
if _, err := decodeManifest([]byte(bad)); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("bad manifest error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSelectorAndManifestRequireCanonicalNewlineTerminatedJSON(t *testing.T) {
|
|
selector := Selector{Version: SchemaVersion, State: "blocked", Transaction: strings.Repeat("a", 32)}
|
|
selectorData, err := encodeSelector(selector)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := decodeSelector(bytes.TrimSuffix(selectorData, []byte("\n"))); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("noncanonical selector error = %v", err)
|
|
}
|
|
snapshot, err := NewSnapshot("oidc", []byte("auth"), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
value, err := newManifest(snapshot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
manifestData, err := encodeManifest(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := decodeManifest(bytes.TrimSuffix(manifestData, []byte("\n"))); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("noncanonical manifest error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestManifestRejectsOIDCUsersAndSnapshotDigestOrSizeMismatch(t *testing.T) {
|
|
snapshot, err := NewSnapshot("oidc", []byte("auth"), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
value, err := newManifest(snapshot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
withUsers := value
|
|
withUsers.Files = append(withUsers.Files, manifestFile{Name: "users.yaml", Size: 1, SHA256: strings.Repeat("a", 64)})
|
|
data, err := json.Marshal(withUsers)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := decodeManifest(append(data, '\n')); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("oidc users error = %v", err)
|
|
}
|
|
for _, mutate := range []func(*manifest){
|
|
func(value *manifest) { value.Files[0].Size++ },
|
|
func(value *manifest) { value.Files[0].SHA256 = strings.Repeat("a", 64) },
|
|
} {
|
|
changed := value
|
|
changed.Files = append([]manifestFile(nil), value.Files...)
|
|
mutate(&changed)
|
|
if err := validateManifest(changed, snapshot); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("changed manifest error = %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestManifestRejectsFileSizesAboveReadBounds(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
mode string
|
|
users []byte
|
|
file int
|
|
tooLarge int
|
|
}{
|
|
{name: "auth", mode: "oidc", file: 0, tooLarge: maximumAuthBytes + 1},
|
|
{name: "users", mode: "local", users: []byte("users"), file: 1, tooLarge: maximumUsersBytes + 1},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
snapshot, err := NewSnapshot(tc.mode, []byte("auth"), tc.users)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
value, err := newManifest(snapshot)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
value.Files[tc.file].Size = tc.tooLarge
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := decodeManifest(append(data, '\n')); !errors.Is(err, ErrIntegrity) {
|
|
t.Fatalf("oversized %s manifest error = %v", tc.name, err)
|
|
}
|
|
})
|
|
}
|
|
}
|