blob: 9469c6f8c4e2418010213807893e54b1f408dc42 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nosql_test
import (
"fmt"
"testing"
"v.io/v23/context"
"v.io/v23/security"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
"v.io/v23/syncbase"
"v.io/v23/syncbase/nosql"
"v.io/v23/verror"
_ "v.io/x/ref/runtime/factories/generic"
tu "v.io/x/ref/services/syncbase/testutil"
)
type serviceTest struct {
f func(ctx *context.T, s syncbase.Service) error
}
type appTest struct {
f func(ctx *context.T, a syncbase.App) error
}
type databaseTest struct {
f func(ctx *context.T, db nosql.Database) error
}
type tableTest struct {
f func(ctx *context.T, tb nosql.Table) error
}
type rowTest struct {
f func(ctx *context.T, r nosql.Row) error
}
type securitySpecTest struct {
layer interface{}
name string
pattern string
}
type securitySpecTestGroup struct {
layer interface{}
name string
patterns []string
mutating bool
}
// TODO(rogulenko): Add more test groups.
var securitySpecTestGroups = []securitySpecTestGroup{
// Service tests.
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
_, err := wire.ServiceClient(s.FullName()).DevModeGetTime(ctx)
return err
}},
name: "service.DevModeGetTime",
patterns: []string{"A_____"},
},
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
return wire.ServiceClient(s.FullName()).DevModeUpdateVClock(ctx, wire.DevModeUpdateVClockOpts{})
}},
name: "service.DevModeUpdateVClock",
patterns: []string{"A_____"},
},
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
_, _, err := s.GetPermissions(ctx)
return err
}},
name: "service.GetPermissions",
patterns: []string{"A_____"},
},
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
return s.SetPermissions(ctx, nil, "")
}},
name: "service.SetPermissions",
patterns: []string{"A_____"},
mutating: true,
},
// App tests.
{
layer: serviceTest{f: func(ctx *context.T, s syncbase.Service) error {
return s.App("newApp").Create(ctx, nil)
}},
name: "app.Create",
patterns: []string{"W_____"},
mutating: true,
},
{
layer: appTest{f: func(ctx *context.T, a syncbase.App) error {
return a.Destroy(ctx)
}},
name: "app.Destroy",
patterns: []string{"_W____"},
mutating: true,
},
{
layer: appTest{f: func(ctx *context.T, a syncbase.App) error {
_, _, err := a.GetPermissions(ctx)
return err
}},
name: "app.GetPermissions",
patterns: []string{"_A____"},
},
{
layer: appTest{f: func(ctx *context.T, a syncbase.App) error {
return a.SetPermissions(ctx, nil, "")
}},
name: "app.SetPermissions",
patterns: []string{"_A____"},
mutating: true,
},
// Database tests.
{
layer: databaseTest{f: func(ctx *context.T, db nosql.Database) error {
_, _, err := db.GetPermissions(ctx)
return err
}},
name: "database.GetPermissions",
patterns: []string{"__A___"},
},
// Table tests.
{
layer: tableTest{f: func(ctx *context.T, tb nosql.Table) error {
_, err := tb.GetPermissions(ctx)
return err
}},
name: "table.GetPermissions",
patterns: []string{"___A__"},
},
// Row tests.
{
layer: rowTest{f: func(ctx *context.T, r nosql.Row) error {
var value string
return r.Get(ctx, &value)
}},
name: "row.Get",
patterns: []string{"___RR_"},
},
}
// TestSecuritySpec tests the Syncbase ACL specification. It runs a set of test
// groups.
//
// Each test group describes a Syncbase public method and a list of security
// patterns that should allow a client to call the method.
// A method might be service.GetPermissions and the pattern for it is "A_____".
// A security pattern is a string of 6 bytes where each byte is an '_' or
// one of X (resolve), R (read), W (write), A (admin).
// The character index stands for:
// 0 - service ACL
// 1 - app ACL
// 2 - database ACL
// 3 - table ACL
// 4 - longest prefix ACL
// 5 - syncgroup ACL
// A pattern defines a set of per-ACL permissions required for a client to call
// the method.
//
// For each test group we generate two sets of tests: allowed tests and denied
// tests.
// Allowed tests are generated by splitting the list of security patterns
// of the test group into one pattern per test.
// Denied tests are generated the following way: we take each allowed pattern
// and generate all patterns that differ in one character, preserving all '_'.
// This way we make sure that every character in a security pattern is
// important.
// Allowed tests are split in two categories: mutating tests and static tests.
// Mutating tests change the state of Syncbase, static don't.
// Denied tests are always static.
//
// Then we pack all tests into runs, where each run has only one type of tests
// (allowed or denied) and at most one mutating test. For each run we rebuild
// the following Syncbase structure:
// service: s; app a; database db; table tb; row prefix (with ACL at 'prefix').
//
// For each test inside a run we generate 6 huge ACLs with a record for each
// of the test + one admin record.
func TestSecuritySpec(t *testing.T) {
deniedTests, staticAllowedTests, mutatingAllowedTests := prepareTests()
runTests(t, false, deniedTests...)
runTests(t, true, staticAllowedTests...)
for _, test := range mutatingAllowedTests {
runTests(t, true, test)
}
}
func prepareTests() (deniedTests, staticAllowedTests, mutatingAllowedTests []securitySpecTest) {
for _, testGroup := range securitySpecTestGroups {
// Collect allowed patterns.
allowedPatterns := map[string]bool{}
for _, pattern := range testGroup.patterns {
allowedPatterns[pattern] = true
}
// Fill in allowed test slices.
for pattern := range allowedPatterns {
test := securitySpecTest{
layer: testGroup.layer,
name: testGroup.name,
pattern: pattern,
}
if testGroup.mutating {
mutatingAllowedTests = append(mutatingAllowedTests, test)
} else {
staticAllowedTests = append(staticAllowedTests, test)
}
}
// Collect denied patterns.
deniedPatterns := map[string]bool{}
for _, pattern := range testGroup.patterns {
for i := 0; i < len(pattern); i++ {
if pattern[i] != '_' {
patternBytes := []byte(pattern)
for _, c := range []byte{'X', 'R', 'W', 'A'} {
patternBytes[i] = c
if !allowedPatterns[string(patternBytes)] {
deniedPatterns[string(patternBytes)] = true
}
}
}
}
}
// Fill in denied test slice.
for pattern := range deniedPatterns {
test := securitySpecTest{
layer: testGroup.layer,
name: testGroup.name,
pattern: pattern,
}
deniedTests = append(deniedTests, test)
}
}
return
}
func runTests(t *testing.T, expectSuccess bool, tests ...securitySpecTest) {
// Create permissions.
servicePerms := tu.DefaultPerms("root:admin")
addPerms(servicePerms, 0, tests...)
appPerms := tu.DefaultPerms("root:admin")
addPerms(appPerms, 1, tests...)
databasePerms := tu.DefaultPerms("root:admin")
addPerms(databasePerms, 2, tests...)
tablePerms := tu.DefaultPerms("root:admin")
addPerms(tablePerms, 3, tests...)
rowPerms := tu.DefaultPerms("root:admin")
addPerms(rowPerms, 4, tests...)
sgPerms := tu.DefaultPerms("root:admin")
addPerms(sgPerms, 5, tests...)
// Create service/app/database/table/row with permissions above.
ctx, adminCtx, sName, rootp, cleanup := tu.SetupOrDieCustom("admin", "server", nil)
defer cleanup()
s := syncbase.NewService(sName)
if err := s.SetPermissions(adminCtx, servicePerms, ""); err != nil {
tu.Fatalf(t, "s.SetPermissions failed: %v", err)
}
a := s.App("a")
if err := a.Create(adminCtx, appPerms); err != nil {
tu.Fatalf(t, "a.Create failed: %v", err)
}
db := a.NoSQLDatabase("db", nil)
if err := db.Create(adminCtx, databasePerms); err != nil {
tu.Fatalf(t, "db.Create failed: %v", err)
}
tb := db.Table("tb")
if err := tb.Create(adminCtx, tablePerms); err != nil {
tu.Fatalf(t, "db.Create failed: %v", err)
}
if err := tb.SetPrefixPermissions(adminCtx, nosql.Prefix(""), rowPerms); err != nil {
tu.Fatalf(t, "tb.SetPrefixPermissions failed: %v", err)
}
if err := tb.SetPrefixPermissions(adminCtx, nosql.Prefix("prefix"), rowPerms); err != nil {
tu.Fatalf(t, "tb.SetPrefixPermissions failed: %v", err)
}
r := tb.Row("prefix")
r.Put(adminCtx, "value")
// Verify tests.
for i, test := range tests {
clientCtx := tu.NewCtx(ctx, rootp, fmt.Sprintf("client%d", i))
var err error
switch layer := test.layer.(type) {
case serviceTest:
err = layer.f(clientCtx, s)
case appTest:
err = layer.f(clientCtx, a)
case databaseTest:
err = layer.f(clientCtx, db)
case tableTest:
err = layer.f(clientCtx, tb)
case rowTest:
err = layer.f(clientCtx, r)
}
if expectSuccess && err != nil {
tu.Fatalf(t, "test %v failed with non-nil error: %v", test, err)
} else if !expectSuccess && verror.ErrorID(err) != verror.ErrNoAccess.ID {
tu.Fatalf(t, "test %v didn't fail with ErrNoAccess error: %v", test, err)
}
}
}
// addPerms add permissions to the perms object for each test.
// For each test we add a blessing pattern "root:clientXX" with a tag
// corresponding test.pattern[index].
func addPerms(perms access.Permissions, index int, tests ...securitySpecTest) {
tagsMap := map[byte]string{
'X': "Resolve",
'R': "Read",
'W': "Write",
'A': "Admin",
}
for i, t := range tests {
if tag, ok := tagsMap[t.pattern[index]]; ok {
perms.Add(security.BlessingPattern(fmt.Sprintf("root:client%d", i)), tag)
}
}
}