Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add read-only security_policy to block admin HTTP endpoints. #5321

Merged
merged 1 commit into from
Oct 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
github.com/golang/mock v1.3.1
github.com/golang/protobuf v1.3.2
github.com/golang/snappy v0.0.0-20170215233205-553a64147049
github.com/google/btree v1.0.0 // indirect
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf // indirect
github.com/gorilla/websocket v0.0.0-20160912153041-2d1e4548da23
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0
Expand All @@ -48,6 +49,8 @@ require (
github.com/minio/minio-go v0.0.0-20190131015406-c8a261de75c1
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.0-20160115111002-cca8bbc07984
github.com/opentracing-contrib/go-grpc v0.0.0-20180928155321-4b5a12d3ff02
github.com/opentracing/opentracing-go v1.1.0
Expand Down
28 changes: 23 additions & 5 deletions go/acl/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ limitations under the License.
// It allows you to register multiple security policies for enforcing
// ACLs for users or HTTP requests. The specific policy to use must be
// specified from a command line argument and cannot be changed on-the-fly.
//
// For actual authentication and authorization, you would need to implement your
// own policy as a package that calls RegisterPolicy(), and compile it into all
// Vitess binaries that you use.
//
// By default (when no security_policy is specified), everyone is allowed to do
// anything.
//
// For convenience, there are two other built-in policies that also do NOT do
// any authentication, but allow you to globally disable some roles entirely:
// * `deny-all` disallows all roles for everyone. Note that access is still
// allowed to endpoints that are considered "public" (no ACL check at all).
// * `read-only` allows anyone to act as DEBUGGING or MONITORING, but no one
// is allowed to act as ADMIN. It also disallows any other custom roles that
// are requested.
package acl

import (
Expand All @@ -39,7 +54,7 @@ const (
)

var (
securityPolicy = flag.String("security_policy", "", "security policy to enforce for URLs")
securityPolicy = flag.String("security_policy", "", "the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)")
policies = make(map[string]Policy)
once sync.Once
currentPolicy Policy
Expand Down Expand Up @@ -69,13 +84,16 @@ func RegisterPolicy(name string, policy Policy) {

func savePolicy() {
if *securityPolicy == "" {
// Setting the policy to nil means Allow All from Anyone.
currentPolicy = nil
return
}
currentPolicy = policies[*securityPolicy]
if currentPolicy == nil {
log.Warningf("policy %s not found, using fallback policy", *securityPolicy)
currentPolicy = FallbackPolicy{}
if policy, ok := policies[*securityPolicy]; ok {
currentPolicy = policy
return
}
log.Warningf("security_policy %q not found; using fallback policy (deny-all)", *securityPolicy)
currentPolicy = denyAllPolicy{}
}

// CheckAccessActor uses the current security policy to
Expand Down
4 changes: 4 additions & 0 deletions go/acl/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func (tp TestPolicy) CheckAccessHTTP(req *http.Request, role string) error {

func init() {
RegisterPolicy("test", TestPolicy{})

// Run the `once` so it doesn't run during testing,
// since we need to override the currentPolicy.
once.Do(savePolicy)
}

func TestSimplePolicy(t *testing.T) {
Expand Down
20 changes: 11 additions & 9 deletions go/acl/fallback_policy.go → go/acl/deny_all_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,21 @@ import (
"net/http"
)

var errFallback = errors.New("not allowed: fallback policy")
var errDenyAll = errors.New("not allowed: deny-all security_policy enforced")

// FallbackPolicy is the policy that's used if the
// requested policy cannot be found. It rejects all
// access.
type FallbackPolicy struct{}
// denyAllPolicy rejects all access.
type denyAllPolicy struct{}

// CheckAccessActor disallows all actor access.
func (fp FallbackPolicy) CheckAccessActor(actor, role string) error {
return errFallback
func (denyAllPolicy) CheckAccessActor(actor, role string) error {
return errDenyAll
}

// CheckAccessHTTP disallows all HTTP access.
func (fp FallbackPolicy) CheckAccessHTTP(req *http.Request, role string) error {
return errFallback
func (denyAllPolicy) CheckAccessHTTP(req *http.Request, role string) error {
return errDenyAll
}

func init() {
RegisterPolicy("deny-all", denyAllPolicy{})
}
52 changes: 52 additions & 0 deletions go/acl/read_only_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package acl

import (
"errors"
"net/http"
)

var errReadOnly = errors.New("not allowed: read-only security_policy enforced")

// readOnlyPolicy allows DEBUGGING and MONITORING roles for everyone,
// while denying any other roles (e.g. ADMIN) for everyone.
type readOnlyPolicy struct{}

// CheckAccessActor disallows all actor access.
func (readOnlyPolicy) CheckAccessActor(actor, role string) error {
switch role {
case DEBUGGING, MONITORING:
return nil
default:
return errReadOnly
}
}

// CheckAccessHTTP disallows all HTTP access.
func (readOnlyPolicy) CheckAccessHTTP(req *http.Request, role string) error {
switch role {
case DEBUGGING, MONITORING:
return nil
default:
return errReadOnly
}
}

func init() {
RegisterPolicy("read-only", readOnlyPolicy{})
}
69 changes: 68 additions & 1 deletion test/tabletmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,14 +685,81 @@ def test_repeated_init_shard_master(self):
# And done.
tablet.kill_tablets([tablet_62344, tablet_62044])

def test_fallback_policy(self):
def test_fallback_security_policy(self):
tablet_62344.create_db('vt_test_keyspace')
tablet_62344.init_tablet('master', 'test_keyspace', '0')

# Requesting an unregistered security_policy should fall back to deny-all.
tablet_62344.start_vttablet(security_policy='bogus')

# It should deny ADMIN role.
f = urllib.urlopen('http://localhost:%d/streamqueryz/terminate' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertIn('not allowed', response)

# It should deny MONITORING role.
f = urllib.urlopen('http://localhost:%d/debug/health' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertIn('not allowed', response)

# It should deny DEBUGGING role.
f = urllib.urlopen('http://localhost:%d/queryz' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertIn('not allowed', response)

tablet_62344.kill_vttablet()

def test_deny_all_security_policy(self):
tablet_62344.create_db('vt_test_keyspace')
tablet_62344.init_tablet('master', 'test_keyspace', '0')
tablet_62344.start_vttablet(security_policy='deny-all')

# It should deny ADMIN role.
f = urllib.urlopen('http://localhost:%d/streamqueryz/terminate' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertIn('not allowed', response)

# It should deny MONITORING role.
f = urllib.urlopen('http://localhost:%d/debug/health' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertIn('not allowed', response)

# It should deny DEBUGGING role.
f = urllib.urlopen('http://localhost:%d/queryz' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertIn('not allowed', response)

tablet_62344.kill_vttablet()

def test_read_only_security_policy(self):
tablet_62344.create_db('vt_test_keyspace')
tablet_62344.init_tablet('master', 'test_keyspace', '0')
tablet_62344.start_vttablet(security_policy='read-only')

# It should deny ADMIN role.
f = urllib.urlopen('http://localhost:%d/streamqueryz/terminate' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertIn('not allowed', response)

# It should allow MONITORING role.
f = urllib.urlopen('http://localhost:%d/debug/health' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertNotIn('not allowed', response)

# It should allow DEBUGGING role.
f = urllib.urlopen('http://localhost:%d/queryz' % int(tablet_62344.port))
response = f.read()
f.close()
self.assertNotIn('not allowed', response)

tablet_62344.kill_vttablet()

def test_ignore_health_error(self):
Expand Down