You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
465 lines
13 KiB
465 lines
13 KiB
// Copyright 2015 PingCAP, 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,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package privileges
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pingcap/parser/auth"
|
|
"github.com/pingcap/parser/mysql"
|
|
"github.com/pingcap/tidb/infoschema"
|
|
"github.com/pingcap/tidb/infoschema/perfschema"
|
|
"github.com/pingcap/tidb/privilege"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util"
|
|
"github.com/pingcap/tidb/util/logutil"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// SkipWithGrant causes the server to start without using the privilege system at all.
|
|
var SkipWithGrant = false
|
|
|
|
var _ privilege.Manager = (*UserPrivileges)(nil)
|
|
|
|
// UserPrivileges implements privilege.Manager interface.
|
|
// This is used to check privilege for the current user.
|
|
type UserPrivileges struct {
|
|
user string
|
|
host string
|
|
*Handle
|
|
}
|
|
|
|
// RequestVerification implements the Manager interface.
|
|
func (p *UserPrivileges) RequestVerification(activeRoles []*auth.RoleIdentity, db, table, column string, priv mysql.PrivilegeType) bool {
|
|
if SkipWithGrant {
|
|
return true
|
|
}
|
|
|
|
if p.user == "" && p.host == "" {
|
|
return true
|
|
}
|
|
|
|
// Skip check for system databases.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/information-schema.html
|
|
dbLowerName := strings.ToLower(db)
|
|
switch dbLowerName {
|
|
case util.InformationSchemaName.L:
|
|
switch priv {
|
|
case mysql.CreatePriv, mysql.AlterPriv, mysql.DropPriv, mysql.IndexPriv, mysql.CreateViewPriv,
|
|
mysql.InsertPriv, mysql.UpdatePriv, mysql.DeletePriv:
|
|
return false
|
|
}
|
|
return true
|
|
// We should be very careful of limiting privileges, so ignore `mysql` for now.
|
|
case util.PerformanceSchemaName.L, util.MetricSchemaName.L:
|
|
if (dbLowerName == util.PerformanceSchemaName.L && perfschema.IsPredefinedTable(table)) ||
|
|
(dbLowerName == util.MetricSchemaName.L && infoschema.IsMetricTable(table)) {
|
|
switch priv {
|
|
case mysql.CreatePriv, mysql.AlterPriv, mysql.DropPriv, mysql.IndexPriv, mysql.InsertPriv, mysql.UpdatePriv, mysql.DeletePriv:
|
|
return false
|
|
case mysql.SelectPriv:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
mysqlPriv := p.Handle.Get()
|
|
return mysqlPriv.RequestVerification(activeRoles, p.user, p.host, db, table, column, priv)
|
|
}
|
|
|
|
// RequestVerificationWithUser implements the Manager interface.
|
|
func (p *UserPrivileges) RequestVerificationWithUser(db, table, column string, priv mysql.PrivilegeType, user *auth.UserIdentity) bool {
|
|
if SkipWithGrant {
|
|
return true
|
|
}
|
|
|
|
if user == nil {
|
|
return false
|
|
}
|
|
|
|
// Skip check for INFORMATION_SCHEMA database.
|
|
// See https://dev.mysql.com/doc/refman/5.7/en/information-schema.html
|
|
if strings.EqualFold(db, "INFORMATION_SCHEMA") {
|
|
return true
|
|
}
|
|
|
|
mysqlPriv := p.Handle.Get()
|
|
return mysqlPriv.RequestVerification(nil, user.Username, user.Hostname, db, table, column, priv)
|
|
}
|
|
|
|
// GetEncodedPassword implements the Manager interface.
|
|
func (p *UserPrivileges) GetEncodedPassword(user, host string) string {
|
|
mysqlPriv := p.Handle.Get()
|
|
record := mysqlPriv.connectionVerification(user, host)
|
|
if record == nil {
|
|
logutil.BgLogger().Error("get user privilege record fail",
|
|
zap.String("user", user), zap.String("host", host))
|
|
return ""
|
|
}
|
|
pwd := record.AuthenticationString
|
|
if len(pwd) != 0 && len(pwd) != mysql.PWDHashLen+1 {
|
|
logutil.BgLogger().Error("user password from system DB not like sha1sum", zap.String("user", user))
|
|
return ""
|
|
}
|
|
return pwd
|
|
}
|
|
|
|
// GetAuthWithoutVerification implements the Manager interface.
|
|
func (p *UserPrivileges) GetAuthWithoutVerification(user, host string) (u string, h string, success bool) {
|
|
if SkipWithGrant {
|
|
p.user = user
|
|
p.host = host
|
|
success = true
|
|
return
|
|
}
|
|
|
|
mysqlPriv := p.Handle.Get()
|
|
record := mysqlPriv.connectionVerification(user, host)
|
|
if record == nil {
|
|
logutil.BgLogger().Error("get user privilege record fail",
|
|
zap.String("user", user), zap.String("host", host))
|
|
return
|
|
}
|
|
|
|
u = record.User
|
|
h = record.Host
|
|
p.user = user
|
|
p.host = h
|
|
success = true
|
|
return
|
|
}
|
|
|
|
// ConnectionVerification implements the Manager interface.
|
|
func (p *UserPrivileges) ConnectionVerification(user, host string, authentication, salt []byte, tlsState *tls.ConnectionState) (u string, h string, success bool) {
|
|
if SkipWithGrant {
|
|
p.user = user
|
|
p.host = host
|
|
success = true
|
|
return
|
|
}
|
|
|
|
mysqlPriv := p.Handle.Get()
|
|
record := mysqlPriv.connectionVerification(user, host)
|
|
if record == nil {
|
|
logutil.BgLogger().Error("get user privilege record fail",
|
|
zap.String("user", user), zap.String("host", host))
|
|
return
|
|
}
|
|
|
|
u = record.User
|
|
h = record.Host
|
|
|
|
globalPriv := mysqlPriv.matchGlobalPriv(user, host)
|
|
if globalPriv != nil {
|
|
if !p.checkSSL(globalPriv, tlsState) {
|
|
logutil.BgLogger().Error("global priv check ssl fail",
|
|
zap.String("user", user), zap.String("host", host))
|
|
success = false
|
|
return
|
|
}
|
|
}
|
|
|
|
// Login a locked account is not allowed.
|
|
locked := record.AccountLocked
|
|
if locked {
|
|
logutil.BgLogger().Error("try to login a locked account",
|
|
zap.String("user", user), zap.String("host", host))
|
|
success = false
|
|
return
|
|
}
|
|
|
|
pwd := record.AuthenticationString
|
|
if len(pwd) != 0 && len(pwd) != mysql.PWDHashLen+1 {
|
|
logutil.BgLogger().Error("user password from system DB not like sha1sum", zap.String("user", user))
|
|
return
|
|
}
|
|
|
|
// empty password
|
|
if len(pwd) == 0 && len(authentication) == 0 {
|
|
p.user = user
|
|
p.host = h
|
|
success = true
|
|
return
|
|
}
|
|
|
|
if len(pwd) == 0 || len(authentication) == 0 {
|
|
return
|
|
}
|
|
|
|
hpwd, err := auth.DecodePassword(pwd)
|
|
if err != nil {
|
|
logutil.BgLogger().Error("decode password string failed", zap.Error(err))
|
|
return
|
|
}
|
|
|
|
if !auth.CheckScrambledPassword(salt, hpwd, authentication) {
|
|
return
|
|
}
|
|
|
|
p.user = user
|
|
p.host = h
|
|
success = true
|
|
return
|
|
}
|
|
|
|
type checkResult int
|
|
|
|
const (
|
|
notCheck checkResult = iota
|
|
pass
|
|
fail
|
|
)
|
|
|
|
func (p *UserPrivileges) checkSSL(priv *globalPrivRecord, tlsState *tls.ConnectionState) bool {
|
|
if priv.Broken {
|
|
logutil.BgLogger().Info("ssl check failure, due to broken global_priv record",
|
|
zap.String("user", priv.User), zap.String("host", priv.Host))
|
|
return false
|
|
}
|
|
switch priv.Priv.SSLType {
|
|
case SslTypeNotSpecified, SslTypeNone:
|
|
return true
|
|
case SslTypeAny:
|
|
r := tlsState != nil
|
|
if !r {
|
|
logutil.BgLogger().Info("ssl check failure, require ssl but not use ssl",
|
|
zap.String("user", priv.User), zap.String("host", priv.Host))
|
|
}
|
|
return r
|
|
case SslTypeX509:
|
|
if tlsState == nil {
|
|
logutil.BgLogger().Info("ssl check failure, require x509 but not use ssl",
|
|
zap.String("user", priv.User), zap.String("host", priv.Host))
|
|
return false
|
|
}
|
|
hasCert := false
|
|
for _, chain := range tlsState.VerifiedChains {
|
|
if len(chain) > 0 {
|
|
hasCert = true
|
|
break
|
|
}
|
|
}
|
|
if !hasCert {
|
|
logutil.BgLogger().Info("ssl check failure, require x509 but no verified cert",
|
|
zap.String("user", priv.User), zap.String("host", priv.Host))
|
|
}
|
|
return hasCert
|
|
case SslTypeSpecified:
|
|
if tlsState == nil {
|
|
logutil.BgLogger().Info("ssl check failure, require subject/issuer/cipher but not use ssl",
|
|
zap.String("user", priv.User), zap.String("host", priv.Host))
|
|
return false
|
|
}
|
|
if len(priv.Priv.SSLCipher) > 0 && priv.Priv.SSLCipher != util.TLSCipher2String(tlsState.CipherSuite) {
|
|
logutil.BgLogger().Info("ssl check failure for cipher", zap.String("user", priv.User), zap.String("host", priv.Host),
|
|
zap.String("require", priv.Priv.SSLCipher), zap.String("given", util.TLSCipher2String(tlsState.CipherSuite)))
|
|
return false
|
|
}
|
|
var (
|
|
hasCert = false
|
|
matchIssuer checkResult
|
|
matchSubject checkResult
|
|
matchSAN checkResult
|
|
)
|
|
for _, chain := range tlsState.VerifiedChains {
|
|
if len(chain) == 0 {
|
|
continue
|
|
}
|
|
cert := chain[0]
|
|
if len(priv.Priv.X509Issuer) > 0 {
|
|
given := util.X509NameOnline(cert.Issuer)
|
|
if priv.Priv.X509Issuer == given {
|
|
matchIssuer = pass
|
|
} else if matchIssuer == notCheck {
|
|
matchIssuer = fail
|
|
logutil.BgLogger().Info("ssl check failure for issuer", zap.String("user", priv.User), zap.String("host", priv.Host),
|
|
zap.String("require", priv.Priv.X509Issuer), zap.String("given", given))
|
|
}
|
|
}
|
|
if len(priv.Priv.X509Subject) > 0 {
|
|
given := util.X509NameOnline(cert.Subject)
|
|
if priv.Priv.X509Subject == given {
|
|
matchSubject = pass
|
|
} else if matchSubject == notCheck {
|
|
matchSubject = fail
|
|
logutil.BgLogger().Info("ssl check failure for subject", zap.String("user", priv.User), zap.String("host", priv.Host),
|
|
zap.String("require", priv.Priv.X509Subject), zap.String("given", given))
|
|
}
|
|
}
|
|
if len(priv.Priv.SANs) > 0 {
|
|
matchOne := checkCertSAN(priv, cert, priv.Priv.SANs)
|
|
if matchOne {
|
|
matchSAN = pass
|
|
} else if matchSAN == notCheck {
|
|
matchSAN = fail
|
|
}
|
|
}
|
|
hasCert = true
|
|
}
|
|
checkResult := hasCert && matchIssuer != fail && matchSubject != fail && matchSAN != fail
|
|
if !checkResult && !hasCert {
|
|
logutil.BgLogger().Info("ssl check failure, require issuer/subject/SAN but no verified cert",
|
|
zap.String("user", priv.User), zap.String("host", priv.Host))
|
|
}
|
|
return checkResult
|
|
default:
|
|
panic(fmt.Sprintf("support ssl_type: %d", priv.Priv.SSLType))
|
|
}
|
|
}
|
|
|
|
func checkCertSAN(priv *globalPrivRecord, cert *x509.Certificate, sans map[util.SANType][]string) (r bool) {
|
|
r = true
|
|
for typ, requireOr := range sans {
|
|
var (
|
|
unsupported bool
|
|
given []string
|
|
)
|
|
switch typ {
|
|
case util.URI:
|
|
for _, uri := range cert.URIs {
|
|
given = append(given, uri.String())
|
|
}
|
|
case util.DNS:
|
|
given = cert.DNSNames
|
|
case util.IP:
|
|
for _, ip := range cert.IPAddresses {
|
|
given = append(given, ip.String())
|
|
}
|
|
default:
|
|
unsupported = true
|
|
}
|
|
if unsupported {
|
|
logutil.BgLogger().Warn("skip unsupported SAN type", zap.String("type", string(typ)),
|
|
zap.String("user", priv.User), zap.String("host", priv.Host))
|
|
continue
|
|
}
|
|
var givenMatchOne bool
|
|
for _, req := range requireOr {
|
|
for _, give := range given {
|
|
if req == give {
|
|
givenMatchOne = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !givenMatchOne {
|
|
logutil.BgLogger().Info("ssl check failure for subject", zap.String("user", priv.User), zap.String("host", priv.Host),
|
|
zap.String("require", priv.Priv.SAN), zap.Strings("given", given), zap.String("type", string(typ)))
|
|
r = false
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// DBIsVisible implements the Manager interface.
|
|
func (p *UserPrivileges) DBIsVisible(activeRoles []*auth.RoleIdentity, db string) bool {
|
|
if SkipWithGrant {
|
|
return true
|
|
}
|
|
mysqlPriv := p.Handle.Get()
|
|
if mysqlPriv.DBIsVisible(p.user, p.host, db) {
|
|
return true
|
|
}
|
|
allRoles := mysqlPriv.FindAllRole(activeRoles)
|
|
for _, role := range allRoles {
|
|
if mysqlPriv.DBIsVisible(role.Username, role.Hostname, db) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// UserPrivilegesTable implements the Manager interface.
|
|
func (p *UserPrivileges) UserPrivilegesTable() [][]types.Datum {
|
|
mysqlPriv := p.Handle.Get()
|
|
return mysqlPriv.UserPrivilegesTable()
|
|
}
|
|
|
|
// ShowGrants implements privilege.Manager ShowGrants interface.
|
|
func (p *UserPrivileges) ShowGrants(ctx sessionctx.Context, user *auth.UserIdentity, roles []*auth.RoleIdentity) (grants []string, err error) {
|
|
if SkipWithGrant {
|
|
return nil, ErrNonexistingGrant.GenWithStackByArgs("root", "%")
|
|
}
|
|
mysqlPrivilege := p.Handle.Get()
|
|
u := user.Username
|
|
h := user.Hostname
|
|
if len(user.AuthUsername) > 0 && len(user.AuthHostname) > 0 {
|
|
u = user.AuthUsername
|
|
h = user.AuthHostname
|
|
}
|
|
grants = mysqlPrivilege.showGrants(u, h, roles)
|
|
if len(grants) == 0 {
|
|
err = ErrNonexistingGrant.GenWithStackByArgs(u, h)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ActiveRoles implements privilege.Manager ActiveRoles interface.
|
|
func (p *UserPrivileges) ActiveRoles(ctx sessionctx.Context, roleList []*auth.RoleIdentity) (bool, string) {
|
|
if SkipWithGrant {
|
|
return true, ""
|
|
}
|
|
mysqlPrivilege := p.Handle.Get()
|
|
u := p.user
|
|
h := p.host
|
|
for _, r := range roleList {
|
|
ok := mysqlPrivilege.FindRole(u, h, r)
|
|
if !ok {
|
|
logutil.BgLogger().Error("find role failed", zap.Stringer("role", r))
|
|
return false, r.String()
|
|
}
|
|
}
|
|
ctx.GetSessionVars().ActiveRoles = roleList
|
|
return true, ""
|
|
}
|
|
|
|
// FindEdge implements privilege.Manager FindRelationship interface.
|
|
func (p *UserPrivileges) FindEdge(ctx sessionctx.Context, role *auth.RoleIdentity, user *auth.UserIdentity) bool {
|
|
if SkipWithGrant {
|
|
return false
|
|
}
|
|
mysqlPrivilege := p.Handle.Get()
|
|
ok := mysqlPrivilege.FindRole(user.Username, user.Hostname, role)
|
|
if !ok {
|
|
logutil.BgLogger().Error("find role failed", zap.Stringer("role", role))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GetDefaultRoles returns all default roles for certain user.
|
|
func (p *UserPrivileges) GetDefaultRoles(user, host string) []*auth.RoleIdentity {
|
|
if SkipWithGrant {
|
|
return make([]*auth.RoleIdentity, 0, 10)
|
|
}
|
|
mysqlPrivilege := p.Handle.Get()
|
|
ret := mysqlPrivilege.getDefaultRoles(user, host)
|
|
return ret
|
|
}
|
|
|
|
// GetAllRoles return all roles of user.
|
|
func (p *UserPrivileges) GetAllRoles(user, host string) []*auth.RoleIdentity {
|
|
if SkipWithGrant {
|
|
return make([]*auth.RoleIdentity, 0, 10)
|
|
}
|
|
|
|
mysqlPrivilege := p.Handle.Get()
|
|
return mysqlPrivilege.getAllRoles(user, host)
|
|
}
|
|
|