最近在研究etcd的权限机制,etcd用的认证方式比较简单basic auth。原理也比较简单易懂,但轻率上线后,发现一个大坑,就是认证时候用来比较密码的哈希是bcript,而且是每个请求都会调。这个耗时很大,导致集群性能下降的厉害,基本不能用了。
查遍官方文档,说3.1+ 用https common name的方式可以解决这个问题,但是我就不想上https,涉及改造的工作量有点大,就想通过auth简单的把误操作的行为隔离一下。
于是乎只能开始动手改了,简单的加一个cache先用。
首先我们用的etcd版本为2.1.1
git clone https://github.com/coreos/etcd.git
git checkout v2.1.1
下载代码后,切换到 v2.1.1 tag下
打开目录 etcdserver
在目录下添加新文件,姑且叫做 password_map.go
添加如下代码
// author:ZeaLoVe
// password map of etcd v2 api
package auth
import (
"fmt"
"sync"
)
// key : string meke by hashed password + plaintext password
// value: error the result of CompareHashAndPassword
type PasswordMap struct {
sync.RWMutex
M map[string]error
}
var DefaultPasswdMap = PasswordMap{
M: make(map[string]error),
}
func (this *PasswordMap) Hit(hashed, pt string) bool {
this.RLock()
defer this.RUnlock()
if _, ok := this.M[hashed+pt]; ok {
return ok
}
return false
}
// Get must called after Hit
func (this *PasswordMap) Get(hashed, pt string) error {
this.RLock()
defer this.RUnlock()
if val, ok := this.M[hashed+pt]; ok {
return val
}
return fmt.Errorf("no hit")
}
func (this *PasswordMap) Set(hashed string, pt string, err error) {
if hashed == "" {
return
}
this.Lock()
defer this.Unlock()
this.M[hashed+pt] = err
}
|
打开该文件夹下的auth.go 找到 CheckPassword函数
修改为
func (u User) CheckPassword(password string) bool {
if DefaultPasswdMap.Hit(u.Password, password) {
return DefaultPasswdMap.Get(u.Password, password) == nil
}
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
DefaultPasswdMap.Set(u.Password, password, err)
return err == nil
}
|
对于其他版本,虽然实现上可能有些区别,但整个原理是一样的(我看过3.1的基本没怎么改变)。此改造不会影响第一次请求的耗时,但后面不会对相同的密码再次调用bcript,会直接返回缓存的结果,极大提高了性能
但该实现没有回收缓存的空间,所以如果有人对同一个账号用各种不同的密码轮番攻击,可能会导致内存持续上升。但经过压测,发现即使试十万次内存也只上涨不到30M。可以暂时忽略这个问题。
即使内存大到一定程度,重启也可以恢复。