Files
tidb/pkg/extension/auth.go

144 lines
6.9 KiB
Go

// Copyright 2024 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,
// 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 extension
import (
"crypto/tls"
"slices"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/parser/auth"
"github.com/pingcap/tidb/pkg/parser/mysql"
"github.com/pingcap/tidb/pkg/privilege/conn"
)
// AuthPlugin contains attributes needed for an authentication plugin.
type AuthPlugin struct {
// Name is the name of the auth plugin. It will be registered as a system variable in TiDB which can be used inside the `CREATE USER ... IDENTIFIED WITH 'plugin_name'` statement.
Name string
// RequiredClientSidePlugin is the name of the client-side plugin required by the server-side plugin. It will be used to check if the client has the required plugin installed and require the client to use it if installed.
// The user can require default MySQL plugins such as 'caching_sha2_password' or 'mysql_native_password'.
// If this is empty then `AuthPlugin.Name` is used as the required client-side plugin.
RequiredClientSidePlugin string
// AuthenticateUser is called when a client connects to the server as a user and the server authenticates the user.
// If an error is returned, the login attempt fails, otherwise it succeeds.
// request: The request context for the authentication plugin to authenticate a user
AuthenticateUser func(request AuthenticateRequest) error
// GenerateAuthString is a function for user to implement customized ways to encode the password (e.g. hash/salt/clear-text). The returned string will be stored as the encoded password in the mysql.user table.
// If the input password is considered as invalid, this should return an error.
// pwd: User's input password in CREATE/ALTER USER statements in clear-text
GenerateAuthString func(pwd string) (string, bool)
// ValidateAuthString checks if the password hash stored in the mysql.user table or passed in from `IDENTIFIED AS` is valid.
// This is called when retrieving an existing user to make sure the password stored is valid and not modified and make sure user is passing a valid password hash in `IDENTIFIED AS`.
// pwdHash: hash of the password stored in the internal user table
ValidateAuthString func(pwdHash string) bool
// VerifyPrivilege is called for each user queries, and serves as an extra check for privileges for the user.
// It will only be executed if the user has already been granted the privilege in SQL layer.
// Returns true if user has the requested privilege.
// request: The request context for the authorization plugin to authorize a user's static privilege
VerifyPrivilege func(request VerifyStaticPrivRequest) bool
// VerifyDynamicPrivilege is called for each user queries, and serves as an extra check for dynamic privileges for the user.
// It will only be executed if the user has already been granted the dynamic privilege in SQL layer.
// Returns true if user has the requested privilege.
// request: The request context for the authorization plugin to authorize a user's dynamic privilege
VerifyDynamicPrivilege func(request VerifyDynamicPrivRequest) bool
}
// AuthenticateRequest contains the context for the authentication plugin to authenticate a user.
type AuthenticateRequest struct {
// User The username in the connect attempt
User string
// StoredAuthString The user's auth string stored in mysql.user table
StoredAuthString string
// InputAuthString The user's auth string passed in from the connection attempt in bytes
InputAuthString []byte
// Salt Randomly generated salt for the current connection
Salt []byte
// ConnState The TLS connection state (contains the TLS certificate) if client is using TLS. It will be nil if the client is not using TLS
ConnState *tls.ConnectionState
// AuthConn Interface for the plugin to communicate with the client
AuthConn conn.AuthConn
}
// VerifyStaticPrivRequest contains the context for the plugin to authorize a user's static privilege.
type VerifyStaticPrivRequest struct {
// User The username in the connect attempt
User string
// Host The host that the user is connecting from
Host string
// DB The database to check for privilege
DB string
// Table The table to check for privilege
Table string
// Column The column to check for privilege (currently just a placeholder in TiDB as column-level privilege is not supported by TiDB yet)
Column string
// StaticPriv The privilege type of the SQL statement that will be executed
StaticPriv mysql.PrivilegeType
// ConnState The TLS connection state (contains the TLS certificate) if client is using TLS. It will be nil if the client is not using TLS
ConnState *tls.ConnectionState
// ActiveRoles List of active MySQL roles for the current user
ActiveRoles []*auth.RoleIdentity
}
// VerifyDynamicPrivRequest contains the context for the plugin to authorize a user's dynamic privilege.
type VerifyDynamicPrivRequest struct {
// User The username in the connect attempt
User string
// Host The host that the user is connecting from
Host string
// DynamicPriv the dynamic privilege required by the user's SQL statement
DynamicPriv string
// ConnState The TLS connection state (contains the TLS certificate) if client is using TLS. It will be nil if the client is not using TLS
ConnState *tls.ConnectionState
// ActiveRoles List of active MySQL roles for the current user
ActiveRoles []*auth.RoleIdentity
// WithGrant Whether the statement to be executed is granting the user privilege for executing GRANT statements
WithGrant bool
}
// validateAuthPlugin validates the auth plugin functions and attributes.
func validateAuthPlugin(m *Manifest) error {
pluginNames := make(map[string]bool)
// Validate required functions for the auth plugins
for _, p := range m.authPlugins {
if p.Name == "" {
return errors.Errorf("auth plugin name cannot be empty for %s", p.Name)
}
if pluginNames[p.Name] {
return errors.Errorf("auth plugin name %s has already been registered", p.Name)
}
pluginNames[p.Name] = true
if slices.Contains(mysql.DefaultAuthPlugins, p.Name) {
return errors.Errorf("auth plugin name %s is a reserved name for default auth plugins", p.Name)
}
if p.AuthenticateUser == nil {
return errors.Errorf("auth plugin AuthenticateUser function cannot be nil for %s", p.Name)
}
if p.GenerateAuthString == nil {
return errors.Errorf("auth plugin GenerateAuthString function cannot be nil for %s", p.Name)
}
if p.ValidateAuthString == nil {
return errors.Errorf("auth plugin ValidateAuthString function cannot be nil for %s", p.Name)
}
}
return nil
}