Files
teleport/lib/backend/atomicwrite_test.go
T
rosstimothy 0486c0935a Update backend.Key to be its own distinct type (#46701)
Converts the backend.Key from a slice of bytes to a concrete struct.
The main motivation behind this change is to be able to better
distinguish individual components of a key. The textual representation
of a backend key is constructed by joining all components of the key
with a /. By representing the entire key as a single textual object
it prevented resources containing a / in their name from being
properly identified. There was a lot of code that interpolated keys
in various manners and expected only a specific number of
subcomponents for a particular resource. This lead to most,
if not all, of the RPCs used to create a resource to permit a name
containing /, (i.e. a user named test/llama/1), but any RPCs used
to retrieve the resources would fail to retrieve them from the
backend.

Every backend.Key now carries a slice of all the individual
components in addition to the textual representation. This permits
more fine grained inspection per component, while also not having
to construct the textual representation for a group of components
more than once.

The backend.Sanitizer was updated to only validate keys for backend
writes. Retrieving and deleting invalid of malformed keys is now
permitted. This will allow any existing resources that were created
with / in one of the components to become retrievable and deletable.
However, any new resources with  / in their name are explicitly
prevented.

Closes #6088
Closes #9107
Closes #10576
Closes #42823
2024-10-04 14:02:00 +00:00

197 lines
4.1 KiB
Go

/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package backend
import (
"strconv"
"testing"
"github.com/stretchr/testify/require"
)
func TestAtomicWriteValidation(t *testing.T) {
t.Parallel()
type testCase struct {
condacts []ConditionalAction
ok bool
desc string
estr string
}
tts := []testCase{
{
condacts: []ConditionalAction{
{
Key: NewKey("foo", "bar"),
Condition: Exists(),
Action: Put(Item{
Value: []byte("true"),
}),
},
{
Key: NewKey("bin", "baz"),
Condition: Revision("r1"),
Action: Delete(),
},
{
Key: NewKey("apples", "oranges"),
Condition: NotExists(),
Action: Nop(),
},
{
Key: NewKey("up", "down"),
Condition: Whatever(),
Action: Put(Item{
Value: []byte("v"),
}),
},
},
ok: true,
desc: "basic case",
},
{
condacts: []ConditionalAction{
{
Key: NewKey("foo", "bar"),
Condition: Revision(""), // empty revisions are allowed
Action: Delete(),
},
},
ok: true,
desc: "empty revision",
},
{
condacts: []ConditionalAction{
{
Key: NewKey("singleton"),
Condition: Whatever(),
Action: Delete(),
},
},
ok: true,
desc: "singleton",
},
{
condacts: nil,
ok: false,
desc: "empty",
estr: "empty conditional action list",
},
{
condacts: []ConditionalAction{
{
Key: NewKey("foo", "bar"),
Condition: Exists(),
Action: Put(Item{
Value: []byte("true"),
}),
},
{
Key: NewKey("foo", "baz"),
Condition: Revision("r1"),
Action: Delete(),
},
{
Key: NewKey("apples", "oranges"),
Condition: NotExists(),
Action: Nop(),
},
{
Key: NewKey("foo", "bar"),
Condition: Revision("r2"),
Action: Nop(),
},
},
ok: false,
desc: "duplicate keys",
estr: "multiple conditional actions target key",
},
{
condacts: []ConditionalAction{
{
Key: NewKey("foo", "bar"),
Condition: Exists(),
Action: Put(Item{}),
},
},
ok: false,
desc: "empty put",
estr: "missing required put parameter Item.Value",
},
{
condacts: []ConditionalAction{
{
Key: NewKey("foo", "bar"),
Condition: Condition{},
Action: Delete(),
},
},
ok: false,
desc: "zero condition",
estr: "missing required parameter 'Condition'",
},
{
condacts: []ConditionalAction{
{
Key: NewKey("foo", "bar"),
Condition: Exists(),
Action: Action{},
},
},
ok: false,
desc: "zero action",
estr: "missing required parameter 'Action'",
},
}
var big []ConditionalAction
for i := 0; i < MaxAtomicWriteSize+1; i++ {
big = append(big, ConditionalAction{
Key: NewKey("key-" + strconv.Itoa(i)),
Condition: Whatever(),
Action: Delete(),
})
}
tts = append(tts, testCase{
condacts: big,
ok: false,
desc: "too big",
})
tts = append(tts, testCase{
condacts: big[:MaxAtomicWriteSize],
ok: true,
desc: "max",
})
for _, tt := range tts {
t.Run(tt.desc, func(t *testing.T) {
err := ValidateAtomicWrite(tt.condacts)
if tt.ok {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), tt.estr)
}
})
}
}