mirror of
https://github.com/Li4n0/revsuit.git
synced 2026-09-22 06:40:43 +08:00
152 lines
5.0 KiB
Go
152 lines
5.0 KiB
Go
/*
|
|
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 agreedto 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 vmysql
|
|
|
|
import (
|
|
"bytes"
|
|
"net"
|
|
"sync"
|
|
|
|
querypb "vitess.io/vitess/go/vt/proto/query"
|
|
)
|
|
|
|
const (
|
|
localhostName = "localhost"
|
|
)
|
|
|
|
// AuthServerStatic implements AuthServer using a static configuration.
|
|
type AuthServerStatic struct {
|
|
// Method can be set to:
|
|
// - MysqlNativePassword
|
|
// - MysqlClearPassword
|
|
// - MysqlDialog
|
|
// It defaults to MysqlNativePassword.
|
|
Method string
|
|
// This mutex helps us prevent data races between the multiple updates of Entries.
|
|
mu sync.Mutex
|
|
// Entries contains the users, passwords and user data.
|
|
Entries map[string][]*AuthServerStaticEntry
|
|
}
|
|
|
|
// AuthServerStaticEntry stores the values for a given user.
|
|
type AuthServerStaticEntry struct {
|
|
// MysqlNativePassword is generated by password hashing methods in MySQL.
|
|
// These changes are illustrated by changes in the result from the PASSWORD() function
|
|
// that computes password hash values and in the structure of the user table where passwords are stored.
|
|
// mysql> SELECT PASSWORD('mypass');
|
|
// +-------------------------------------------+
|
|
// | PASSWORD('mypass') |
|
|
// +-------------------------------------------+
|
|
// | *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 |
|
|
// +-------------------------------------------+
|
|
// MysqlNativePassword's format looks like "*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4", it store a hashing value.
|
|
// Use MysqlNativePassword in auth config, maybe more secure. After all, it is cryptographic storage.
|
|
MysqlNativePassword string
|
|
Password string
|
|
UserData string
|
|
SourceHost string
|
|
Groups []string
|
|
}
|
|
|
|
// AuthMethod is part of the AuthServer interface.
|
|
func (a *AuthServerStatic) AuthMethod(user string) (string, error) {
|
|
return a.Method, nil
|
|
}
|
|
|
|
// Salt is part of the AuthServer interface.
|
|
func (a *AuthServerStatic) Salt() ([]byte, error) {
|
|
return NewSalt()
|
|
}
|
|
|
|
// ValidateHash is part of the AuthServer interface.
|
|
func (a *AuthServerStatic) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) {
|
|
a.mu.Lock()
|
|
entries, ok := a.Entries[user]
|
|
a.mu.Unlock()
|
|
|
|
if !ok {
|
|
return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.MysqlNativePassword != "" {
|
|
isPass := isPassScrambleMysqlNativePassword(authResponse, salt, entry.MysqlNativePassword)
|
|
if matchSourceHost(remoteAddr, entry.SourceHost) && isPass {
|
|
return &StaticUserData{entry.UserData, entry.Groups}, nil
|
|
}
|
|
} else {
|
|
computedAuthResponse := ScramblePassword(salt, []byte(entry.Password))
|
|
// Validate the password.
|
|
if matchSourceHost(remoteAddr, entry.SourceHost) && bytes.Equal(authResponse, computedAuthResponse) {
|
|
return &StaticUserData{entry.UserData, entry.Groups}, nil
|
|
}
|
|
}
|
|
}
|
|
return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
|
|
}
|
|
|
|
// Negotiate is part of the AuthServer interface.
|
|
// It will be called if Method is anything else than MysqlNativePassword.
|
|
// We only recognize MysqlClearPassword and MysqlDialog here.
|
|
func (a *AuthServerStatic) Negotiate(c *Conn, user string, remoteAddr net.Addr) (Getter, error) {
|
|
// Finish the negotiation.
|
|
password, err := AuthServerNegotiateClearOrDialog(c, a.Method)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
a.mu.Lock()
|
|
entries, ok := a.Entries[user]
|
|
a.mu.Unlock()
|
|
|
|
if !ok {
|
|
return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
|
|
}
|
|
for _, entry := range entries {
|
|
// Validate the password.
|
|
if matchSourceHost(remoteAddr, entry.SourceHost) && entry.Password == password {
|
|
return &StaticUserData{entry.UserData, entry.Groups}, nil
|
|
}
|
|
}
|
|
return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user)
|
|
}
|
|
|
|
func matchSourceHost(remoteAddr net.Addr, targetSourceHost string) bool {
|
|
// Legacy support, there was not matcher defined default to true
|
|
if targetSourceHost == "" {
|
|
return true
|
|
}
|
|
switch remoteAddr.(type) {
|
|
case *net.UnixAddr:
|
|
if targetSourceHost == localhostName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// StaticUserData holds the username and groups
|
|
type StaticUserData struct {
|
|
username string
|
|
groups []string
|
|
}
|
|
|
|
// Get returns the wrapped username and groups
|
|
func (sud *StaticUserData) Get() *querypb.VTGateCallerID {
|
|
return &querypb.VTGateCallerID{Username: sud.username, Groups: sud.groups}
|
|
}
|