1. move simpleacl.go from go/vt/tableacl to go/vt/simpleacl
2. add an AclFactory interface.
3. move both ACL and AclFactory interfaces to go/vt/tableacl/acl
4. define a package level variable called "acls" in tableacl which
   contains all registered AclFactory.
5. enable simpleacl as the default tableacl mechanism.

To plugin the specific tableacl implementation other than simpleacl, one
could either modify all binaries (go/cmd/vtocc/vtocc.go and go/cmd/vttablet/vttablet.go)
to call tableacl.Register(...) and then point tableacl.DefaultACL to the
specific tableacl. Alternatively, one could also put the specific tableacl
implementation under go/vt/tableacl dir and use the init function to finish
the registeration.
This commit is contained in:
Shengzhe Yao 2015-04-13 19:49:18 -07:00
Родитель 7f6f6696b2
Коммит b702dbf3d9
9 изменённых файлов: 206 добавлений и 97 удалений

Просмотреть файл

@ -17,6 +17,7 @@ import (
"github.com/youtube/vitess/go/vt/mysqlctl"
"github.com/youtube/vitess/go/vt/servenv"
"github.com/youtube/vitess/go/vt/tableacl"
"github.com/youtube/vitess/go/vt/tableacl/simpleacl"
"github.com/youtube/vitess/go/vt/tabletserver"
// import mysql to register mysql connection function
@ -76,6 +77,7 @@ func main() {
log.Infof("schemaOverrides: %s\n", data)
if *tableAclConfig != "" {
tableacl.Register("simpleacl", &simpleacl.Factory{})
tableacl.Init(*tableAclConfig)
}
qsc := tabletserver.NewQueryServiceControl()

Просмотреть файл

@ -15,6 +15,7 @@ import (
"github.com/youtube/vitess/go/vt/mysqlctl"
"github.com/youtube/vitess/go/vt/servenv"
"github.com/youtube/vitess/go/vt/tableacl"
"github.com/youtube/vitess/go/vt/tableacl/simpleacl"
"github.com/youtube/vitess/go/vt/tabletmanager"
"github.com/youtube/vitess/go/vt/tabletmanager/actionnode"
"github.com/youtube/vitess/go/vt/tabletserver"
@ -85,6 +86,7 @@ func main() {
dbcfgs.App.EnableRowcache = *enableRowcache
if *tableAclConfig != "" {
tableacl.Register("simpleacl", &simpleacl.Factory{})
tableacl.Init(*tableAclConfig)
}

21
go/vt/tableacl/acl/acl.go Normal file
Просмотреть файл

@ -0,0 +1,21 @@
// Copyright 2015, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package acl
// ACL is an interface for Access Control List.
type ACL interface {
// IsMember checks the membership of a principal in this ACL.
IsMember(principal string) bool
}
// Factory is responsible to create new ACL instance.
type Factory interface {
// New creates a new ACL instance.
New(entries []string) (ACL, error)
// All returns an ACL instance that contains all users.
All() ACL
// AllString returns a string representation of all users.
AllString() string
}

Просмотреть файл

@ -1,31 +0,0 @@
package tableacl
var allAcl simpleACL
const (
ALL = "*"
)
// NewACL returns an ACL with the specified entries
func NewACL(entries []string) (ACL, error) {
a := simpleACL(map[string]bool{})
for _, e := range entries {
a[e] = true
}
return a, nil
}
// simpleACL keeps all entries in a unique in-memory list
type simpleACL map[string]bool
// IsMember checks the membership of a principal in this ACL
func (a simpleACL) IsMember(principal string) bool {
return a[principal] || a[ALL]
}
func all() ACL {
if allAcl == nil {
allAcl = simpleACL(map[string]bool{ALL: true})
}
return allAcl
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright 2015, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simpleacl
import "github.com/youtube/vitess/go/vt/tableacl/acl"
var allAcl SimpleAcl
const all = "*"
// SimpleAcl keeps all entries in a unique in-memory list
type SimpleAcl map[string]bool
// IsMember checks the membership of a principal in this ACL
func (sacl SimpleAcl) IsMember(principal string) bool {
return sacl[principal] || sacl[all]
}
// Factory is responsible to create new ACL instance.
type Factory struct{}
// New creates a new ACL instance.
func (factory *Factory) New(entries []string) (acl.ACL, error) {
acl := SimpleAcl(map[string]bool{})
for _, e := range entries {
acl[e] = true
}
return acl, nil
}
// All returns an ACL instance that contains all users.
func (factory *Factory) All() acl.ACL {
if allAcl == nil {
allAcl = SimpleAcl(map[string]bool{all: true})
}
return allAcl
}
// AllString returns a string representation of all users.
func (factory *Factory) AllString() string {
return all
}
// make sure SimpleAcl implements interface acl.ACL
var _ (acl.ACL) = (*SimpleAcl)(nil)
// make sure Factory implements interface acl.AclFactory
var _ (acl.Factory) = (*Factory)(nil)

Просмотреть файл

@ -1,3 +1,7 @@
// Copyright 2015, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tableacl
import (
@ -7,15 +11,17 @@ import (
"log"
"regexp"
"strings"
"sync"
"github.com/youtube/vitess/go/vt/tableacl/acl"
)
// ACL is an interface for Access Control List.
type ACL interface {
// IsMember checks the membership of a principal in this ACL
IsMember(principal string) bool
}
var mu sync.Mutex
var tableAcl map[*regexp.Regexp]map[Role]acl.ACL
var acls = make(map[string]acl.Factory)
var tableAcl map[*regexp.Regexp]map[Role]ACL
// DefaultACL tells the default ACL implementation to use.
var DefaultACL string
// Init initiates table ACLs.
func Init(configFile string) {
@ -42,19 +48,19 @@ func InitFromBytes(config []byte) (err error) {
// <tableRegexPattern1>: {"READER": "*", "WRITER": "<u2>,<u4>...","ADMIN": "<u5>"},
// <tableRegexPattern2>: {"ADMIN": "<u5>"}
//}`)
func load(config []byte) (map[*regexp.Regexp]map[Role]ACL, error) {
func load(config []byte) (map[*regexp.Regexp]map[Role]acl.ACL, error) {
var contents map[string]map[string]string
err := json.Unmarshal(config, &contents)
if err != nil {
return nil, err
}
tableAcl := make(map[*regexp.Regexp]map[Role]ACL)
tableAcl := make(map[*regexp.Regexp]map[Role]acl.ACL)
for tblPattern, accessMap := range contents {
re, err := regexp.Compile(tblPattern)
if err != nil {
return nil, fmt.Errorf("regexp compile error %v: %v", tblPattern, err)
}
tableAcl[re] = make(map[Role]ACL)
tableAcl[re] = make(map[Role]acl.ACL)
entriesByRole := make(map[Role][]string)
for i := READER; i < NumRoles; i++ {
@ -71,7 +77,7 @@ func load(config []byte) (map[*regexp.Regexp]map[Role]ACL, error) {
}
}
for r, entries := range entriesByRole {
a, err := NewACL(entries)
a, err := newACL(entries)
if err != nil {
return nil, err
}
@ -83,8 +89,8 @@ func load(config []byte) (map[*regexp.Regexp]map[Role]ACL, error) {
}
// Authorized returns the list of entities who have at least the
// minimum specified Role on a table.
func Authorized(table string, minRole Role) ACL {
// minimum specified Role on a tablel.
func Authorized(table string, minRole Role) acl.ACL {
// If table ACL is disabled, return nil
if tableAcl == nil {
return nil
@ -98,3 +104,44 @@ func Authorized(table string, minRole Role) ACL {
// No matching patterns for table, allow all access
return all()
}
// Register registers a AclFactory.
func Register(name string, factory acl.Factory) {
mu.Lock()
defer mu.Unlock()
if _, ok := acls[name]; ok {
panic(fmt.Sprintf("register a registered key: %s", name))
}
acls[name] = factory
}
func newACL(entries []string) (acl.ACL, error) {
return getCurrentAclFactory().New(entries)
}
func all() acl.ACL {
return getCurrentAclFactory().All()
}
func allString() string {
return getCurrentAclFactory().AllString()
}
func getCurrentAclFactory() acl.Factory {
mu.Lock()
defer mu.Unlock()
if DefaultACL == "" {
if len(acls) == 1 {
for _, aclFactory := range acls {
return aclFactory
}
}
panic("there are more than one AclFactory " +
"registered but no default has been given.")
}
aclFactory, ok := acls[DefaultACL]
if !ok {
panic(fmt.Sprintf("aclFactory for given default: %s is not found.", DefaultACL))
}
return aclFactory
}

Просмотреть файл

@ -4,12 +4,23 @@
package tableacl
import "testing"
import (
"os"
"testing"
"github.com/youtube/vitess/go/vt/tableacl/simpleacl"
)
func currentUser() string {
return "DummyUser"
}
func TestMain(m *testing.M) {
name := "simpleacl-tableacl-test"
Register(name, &simpleacl.Factory{})
os.Exit(m.Run())
}
func TestParseInvalidJSON(t *testing.T) {
checkLoad([]byte(`{1:2}`), false, t)
checkLoad([]byte(`{"1":"2"}`), false, t)
@ -28,10 +39,10 @@ func TestValidConfigs(t *testing.T) {
checkLoad([]byte(`{"table1":{"READER":"u1"}}`), true, t)
checkLoad([]byte(`{"table1":{"READER":"u1,u2", "WRITER":"u3"}}`), true, t)
checkLoad([]byte(`{"table[0-9]+":{"Reader":"u1,u2", "WRITER":"u3"}}`), true, t)
checkLoad([]byte(`{"table[0-9]+":{"Reader":"u1,`+ALL+`", "WRITER":"u3"}}`), true, t)
checkLoad([]byte(`{"table[0-9]+":{"Reader":"u1,`+allString()+`", "WRITER":"u3"}}`), true, t)
checkLoad([]byte(`{
"table[0-9]+":{"Reader":"u1,`+ALL+`", "WRITER":"u3"},
"tbl[0-9]+":{"Reader":"u1,`+ALL+`", "WRITER":"u3", "ADMIN":"u4"}
"table[0-9]+":{"Reader":"u1,`+allString()+`", "WRITER":"u3"},
"tbl[0-9]+":{"Reader":"u1,`+allString()+`", "WRITER":"u3", "ADMIN":"u4"}
}`), true, t)
}
@ -56,12 +67,12 @@ func TestAllowUnmatchedTable(t *testing.T) {
}
func TestAllUserReadAccess(t *testing.T) {
configData := []byte(`{"table[0-9]+":{"Reader":"` + ALL + `", "WRITER":"u3"}}`)
configData := []byte(`{"table[0-9]+":{"Reader":"` + allString() + `", "WRITER":"u3"}}`)
checkAccess(configData, "table1", READER, t, true)
}
func TestAllUserWriteAccess(t *testing.T) {
configData := []byte(`{"table[0-9]+":{"Reader":"` + currentUser() + `", "WRITER":"` + ALL + `"}}`)
configData := []byte(`{"table[0-9]+":{"Reader":"` + currentUser() + `", "WRITER":"` + allString() + `"}}`)
checkAccess(configData, "table1", WRITER, t, true)
}

Просмотреть файл

@ -15,6 +15,8 @@ import (
mproto "github.com/youtube/vitess/go/mysql/proto"
"github.com/youtube/vitess/go/sqltypes"
"github.com/youtube/vitess/go/vt/callinfo"
"github.com/youtube/vitess/go/vt/tableacl"
"github.com/youtube/vitess/go/vt/tableacl/simpleacl"
"github.com/youtube/vitess/go/vt/tabletserver/fakecacheservice"
"github.com/youtube/vitess/go/vt/tabletserver/fakesqldb"
"github.com/youtube/vitess/go/vt/tabletserver/planbuilder"
@ -567,53 +569,57 @@ func TestQueryExecutorPlanOther(t *testing.T) {
checkEqual(t, expected, qre.Execute())
}
//func TestQueryExecutorTableAcl(t *testing.T) {
// db := setUpQueryExecutorTest()
// query := "select * from test_table limit 1000"
// expected := &mproto.QueryResult{
// Fields: getTestTableFields(),
// RowsAffected: 0,
// Rows: [][]sqltypes.Value{},
// }
// db.AddQuery(query, expected)
// db.AddQuery("select * from test_table where 1 != 1", &mproto.QueryResult{
// Fields: getTestTableFields(),
// })
//
// username := "u2"
// callInfo := &fakeCallInfo{
// remoteAddr: "1.2.3.4",
// username: username,
// }
// ctx := callinfo.NewContext(context.Background(), callInfo)
// if err := tableacl.InitFromBytes(
// []byte(fmt.Sprintf(`{"test_table":{"READER":"%s"}}`, username))); err != nil {
// t.Fatalf("unable to load tableacl config, error: %v", err)
// }
//
// qre, sqlQuery := newTestQueryExecutor(
// query, ctx, enableRowCache|enableSchemaOverrides|enableStrict)
// checkPlanID(t, planbuilder.PLAN_PASS_SELECT, qre.plan.PlanId)
// checkEqual(t, expected, qre.Execute())
// sqlQuery.disallowQueries()
//
// if err := tableacl.InitFromBytes([]byte(`{"test_table":{"READER":"superuser"}}`)); err != nil {
// t.Fatalf("unable to load tableacl config, error: %v", err)
// }
// // without enabling Config.StrictTableAcl
// qre, sqlQuery = newTestQueryExecutor(
// query, ctx, enableRowCache|enableSchemaOverrides|enableStrict)
// checkPlanID(t, planbuilder.PLAN_PASS_SELECT, qre.plan.PlanId)
// qre.Execute()
// sqlQuery.disallowQueries()
// // enable Config.StrictTableAcl
// qre, sqlQuery = newTestQueryExecutor(
// query, ctx, enableRowCache|enableSchemaOverrides|enableStrict|enableStrictTableAcl)
// defer sqlQuery.disallowQueries()
// checkPlanID(t, planbuilder.PLAN_PASS_SELECT, qre.plan.PlanId)
// defer handleAndVerifyTabletError(t, "query should fail because current user do not have read permissions", ErrFail)
// qre.Execute()
//}
func TestQueryExecutorTableAcl(t *testing.T) {
aclName := fmt.Sprintf("simpleacl-test-%d", rand.Int63())
tableacl.Register(aclName, &simpleacl.Factory{})
tableacl.DefaultACL = aclName
db := setUpQueryExecutorTest()
query := "select * from test_table limit 1000"
expected := &mproto.QueryResult{
Fields: getTestTableFields(),
RowsAffected: 0,
Rows: [][]sqltypes.Value{},
}
db.AddQuery(query, expected)
db.AddQuery("select * from test_table where 1 != 1", &mproto.QueryResult{
Fields: getTestTableFields(),
})
username := "u2"
callInfo := &fakeCallInfo{
remoteAddr: "1.2.3.4",
username: username,
}
ctx := callinfo.NewContext(context.Background(), callInfo)
if err := tableacl.InitFromBytes(
[]byte(fmt.Sprintf(`{"test_table":{"READER":"%s"}}`, username))); err != nil {
t.Fatalf("unable to load tableacl config, error: %v", err)
}
qre, sqlQuery := newTestQueryExecutor(
query, ctx, enableRowCache|enableSchemaOverrides|enableStrict)
checkPlanID(t, planbuilder.PLAN_PASS_SELECT, qre.plan.PlanId)
checkEqual(t, expected, qre.Execute())
sqlQuery.disallowQueries()
if err := tableacl.InitFromBytes([]byte(`{"test_table":{"READER":"superuser"}}`)); err != nil {
t.Fatalf("unable to load tableacl config, error: %v", err)
}
// without enabling Config.StrictTableAcl
qre, sqlQuery = newTestQueryExecutor(
query, ctx, enableRowCache|enableSchemaOverrides|enableStrict)
checkPlanID(t, planbuilder.PLAN_PASS_SELECT, qre.plan.PlanId)
qre.Execute()
sqlQuery.disallowQueries()
// enable Config.StrictTableAcl
qre, sqlQuery = newTestQueryExecutor(
query, ctx, enableRowCache|enableSchemaOverrides|enableStrict|enableStrictTableAcl)
defer sqlQuery.disallowQueries()
checkPlanID(t, planbuilder.PLAN_PASS_SELECT, qre.plan.PlanId)
defer handleAndVerifyTabletError(t, "query should fail because current user do not have read permissions", ErrFail)
qre.Execute()
}
func TestQueryExecutorBlacklistQRFail(t *testing.T) {
db := setUpQueryExecutorTest()

Просмотреть файл

@ -22,6 +22,7 @@ import (
"github.com/youtube/vitess/go/timer"
"github.com/youtube/vitess/go/vt/schema"
"github.com/youtube/vitess/go/vt/tableacl"
tacl "github.com/youtube/vitess/go/vt/tableacl/acl"
"github.com/youtube/vitess/go/vt/tabletserver/planbuilder"
"golang.org/x/net/context"
)
@ -44,7 +45,7 @@ type ExecPlan struct {
TableInfo *TableInfo
Fields []mproto.Field
Rules *QueryRules
Authorized tableacl.ACL
Authorized tacl.ACL
mu sync.Mutex
QueryCount int64