finished / abandoning
the default password mode in mysql 8 will not work with this approach, it requires the plaintext passwords be in proxysql
This commit is contained in:
@ -13,8 +13,41 @@ type User struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
type UserClient interface {
|
||||
GetUsers() ([]User, error)
|
||||
type MatchResult int
|
||||
|
||||
const (
|
||||
MATCH_NONE MatchResult = iota
|
||||
MATCH_USER
|
||||
MATCH_ALL
|
||||
)
|
||||
|
||||
// Matches describes if/how a User matches another user if the username and passwords match, MATCH_ALL is returned. If
|
||||
// the username matches but not the password, MATCH_USER is returned. If the username doesn't match, MATCH_NONE.
|
||||
func (u *User) Matches(b *User) MatchResult {
|
||||
if u.User == b.User {
|
||||
if u.Password == b.Password {
|
||||
return MATCH_ALL
|
||||
}
|
||||
return MATCH_USER
|
||||
}
|
||||
return MATCH_NONE
|
||||
}
|
||||
|
||||
// matchUser searches $users for a user matching $user, and returns the matched user
|
||||
func matchUser(users []*User, user *User) *User {
|
||||
for _, otheruser := range users {
|
||||
if otheruser.Matches(user) != MATCH_NONE {
|
||||
return otheruser
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserFetcher interface {
|
||||
GetUsers() ([]*User, error)
|
||||
}
|
||||
|
||||
type UserSetter interface {
|
||||
CreateUser(string, string, string) error
|
||||
DeleteUser(string, string) error
|
||||
}
|
||||
@ -23,10 +56,10 @@ type MysqlClient struct {
|
||||
con *sql.DB
|
||||
}
|
||||
|
||||
func (m MysqlClient) GetUsers() ([]User, error) {
|
||||
var users []User
|
||||
func (m MysqlClient) GetUsers() ([]*User, error) {
|
||||
var users []*User
|
||||
|
||||
rows, err := m.con.Query("select User,Host,authentication_string from mysql.user;")
|
||||
rows, err := m.con.Query(`SELECT User, Host, authentication_string FROM mysql.user WHERE Host="%";`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't run query: %s", err)
|
||||
}
|
||||
@ -37,7 +70,7 @@ func (m MysqlClient) GetUsers() ([]User, error) {
|
||||
if err := rows.Scan(&user.User, &user.Host, &user.Password); err != nil {
|
||||
return nil, fmt.Errorf("couldn't read row: %s", err)
|
||||
}
|
||||
users = append(users, user)
|
||||
users = append(users, &user)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("couldn't read rows: %s", err)
|
||||
@ -49,12 +82,12 @@ type ProxysqlClient struct {
|
||||
con *sql.DB
|
||||
}
|
||||
|
||||
func (p ProxysqlClient) GetUsers() ([]User, error) {
|
||||
func (p ProxysqlClient) GetUsers() ([]*User, error) {
|
||||
// This differs from the mysql version above in that proxysql doesn't support by-host user filtering, so we just
|
||||
// assume every user's host is %
|
||||
var users []User
|
||||
var users []*User
|
||||
|
||||
rows, err := p.con.Query("select username, password from mysql_users;")
|
||||
rows, err := p.con.Query("SELECT username, password FROM mysql_users;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't run query: %s", err)
|
||||
}
|
||||
@ -66,7 +99,7 @@ func (p ProxysqlClient) GetUsers() ([]User, error) {
|
||||
if err := rows.Scan(&user.User, &user.Password); err != nil {
|
||||
return nil, fmt.Errorf("couldn't read row: %s", err)
|
||||
}
|
||||
users = append(users, user)
|
||||
users = append(users, &user)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("couldn't read rows: %s", err)
|
||||
@ -75,11 +108,21 @@ func (p ProxysqlClient) GetUsers() ([]User, error) {
|
||||
}
|
||||
|
||||
func (p ProxysqlClient) CreateUser(username, host, password string) error {
|
||||
// TODO
|
||||
//_, err := p.con.Exec("REPLACE INTO mysql_users (username, password) VALUES (?, ?);", username, password)
|
||||
_, err := p.con.Exec(`REPLACE INTO mysql_users (username, password) VALUES ("` + username + `", "` + password + `");`)
|
||||
if err != nil {
|
||||
ver, _ := getVersion(p.con)
|
||||
log.Printf("server version: %s", ver)
|
||||
return fmt.Errorf("add user query failed: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p ProxysqlClient) DeleteUser(username, host string) error {
|
||||
// TODO
|
||||
_, err := p.con.Exec("DELETE FROM mysql_users WHERE username=?;", username)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete user query failed: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -97,6 +140,26 @@ func getMysqlClient(dsn string) (*sql.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func getVersion(db *sql.DB) (string, error) {
|
||||
var version string
|
||||
|
||||
rows, err := db.Query("SELECT version();")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("couldn't run query: %s", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&version); err != nil {
|
||||
return "", fmt.Errorf("couldn't read row: %s", err)
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return "", fmt.Errorf("couldn't read rows: %s", err)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func Sync(srcDSN, destDSN string) error {
|
||||
// connect to both mysqls
|
||||
srcdb, err := getMysqlClient(srcDSN)
|
||||
@ -120,22 +183,73 @@ func Sync(srcDSN, destDSN string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't retreive src users: %s", err)
|
||||
}
|
||||
for _, user := range srcUsers {
|
||||
log.Printf("src: %v", user)
|
||||
}
|
||||
|
||||
destUsers, err := dest.GetUsers()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't retreive dest users: %s", err)
|
||||
}
|
||||
for _, user := range destUsers {
|
||||
log.Printf("dest: %v", user)
|
||||
}
|
||||
|
||||
// build list of operations we'll do
|
||||
// remove user
|
||||
// add user
|
||||
// update user password
|
||||
|
||||
// first, pair up users that exist on both sides
|
||||
// and use it to check for users that need a password updated
|
||||
// find users that need to be created on the dest side
|
||||
// find users that need to be deleted on the dest side
|
||||
var pairs [][]*User
|
||||
var srcOnly []*User
|
||||
var destOnly []*User
|
||||
|
||||
for _, srcUser := range srcUsers {
|
||||
destUser := matchUser(destUsers, srcUser)
|
||||
if destUser != nil {
|
||||
if srcUser.Password != destUser.Password {
|
||||
pairs = append(pairs, []*User{srcUser, destUser})
|
||||
}
|
||||
} else {
|
||||
srcOnly = append(srcOnly, srcUser)
|
||||
}
|
||||
}
|
||||
|
||||
for _, destUser := range destUsers {
|
||||
srcUser := matchUser(srcUsers, destUser)
|
||||
if srcUser == nil {
|
||||
destOnly = append(destOnly, destUser)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("update password: ")
|
||||
for _, user := range pairs {
|
||||
log.Printf("\t%s", user[0].User)
|
||||
err := dest.CreateUser(user[0].User, user[0].Host, user[0].Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't update password: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("create on destination: ")
|
||||
for _, user := range srcOnly {
|
||||
log.Printf("\t%s", user.User)
|
||||
err := dest.CreateUser(user.User, user.Host, user.Password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't create user: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("delete on destination:")
|
||||
for _, user := range destOnly {
|
||||
log.Printf("\t%s", user.User)
|
||||
err := dest.DeleteUser(user.User, user.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't delete user: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pairs) > 0 || len(srcOnly) > 0 || len(destOnly) > 0 {
|
||||
_, err := destdb.Exec("LOAD MYSQL USERS TO RUNTIME;")
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't update runtime users: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// perform the operations
|
||||
|
||||
|
Reference in New Issue
Block a user