diff --git a/README.md b/README.md index 79af4dd..25d8a4b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Nginx LDAP Auth -Use this in order to provide a ingress authentication over LDAP for Kubernetes, change the ConfigMap inside `k8s.yaml` to match your LDAP server and run: +Use this in order to provide a ingress authentication over LDAP for Kubernetes, change the Secret inside `k8s.yaml` to match your LDAP server and run: kubectl apply -f k8s.yaml diff --git a/src/main.go b/src/main.go index fb5d767..0ae8bb3 100644 --- a/src/main.go +++ b/src/main.go @@ -10,6 +10,7 @@ import ( ) var configFile = flag.String("config", "/etc/nginx-ldap-auth/config.yaml", "Configuration file") +var config Config func main() { flag.Parse() @@ -19,7 +20,6 @@ func main() { log.Fatalf("Could not read file \"%s\": %v\n", *configFile, err) } - var config Config err = yaml.Unmarshal(data, &config) if err != nil { log.Fatalf("Error on parse config: %v\n", err) diff --git a/src/timeout.go b/src/timeout.go new file mode 100644 index 0000000..c49fe2c --- /dev/null +++ b/src/timeout.go @@ -0,0 +1,119 @@ +package main + +import ( + "sort" + "sync" + "time" +) + +type passtimer struct { + password string + timer *time.Timer +} + +type userpass struct { + correct *passtimer + wrong []passtimer +} + +var ( + passwords map[string]userpass + mutex = sync.RWMutex{} +) + +func containsWrongPassword(data *userpass, password string) (int, bool) { + size := len(data.wrong) + if size == 0 { + return 0, false + } + + pos := sort.Search(size, func(i int) bool { + return data.wrong[i].password < password + }) + + return pos, pos < size && + data.wrong[pos].password == password +} + +func getCache(username, password string) (bool, bool) { + defer mutex.RUnlock() + mutex.RLock() + + data, found := passwords[username] + if !found { + return false, false + } + + if data.correct != nil && (*data.correct).password == password { + return true, true + } + + _, found = containsWrongPassword(&data, password) + + return false, found +} + +func putCache(username, password string, ok bool) { + defer mutex.Unlock() + mutex.Lock() + + data, found := passwords[username] + if !found { + data = userpass{} + passwords[username] = data + } + + if ok { + if data.correct != nil { + data.correct.timer.Stop() + } + data.correct = &passtimer{ + password: password, + timer: time.AfterFunc(config.Timeout.Success, func() { + removeCache(username, "", true) + }), + } + } else { + pass := passtimer{ + password: password, + timer: time.AfterFunc(config.Timeout.Wrong, func() { + removeCache(username, password, false) + }), + } + pos, found := containsWrongPassword(&data, password) + if found { + data.wrong[pos].timer.Stop() + } else { + data.wrong = append(data.wrong, passtimer{}) + copy(data.wrong[pos+1:], data.wrong[pos:]) + } + data.wrong[pos] = pass + } +} + +func removeCache(username, password string, ok bool) { + defer mutex.Unlock() + mutex.Lock() + + data, found := passwords[username] + if !found { + return + } + + if ok { + if data.correct != nil { + data.correct.timer.Stop() + data.correct = nil + } + } else { + pos, found := containsWrongPassword(&data, password) + if found { + data.wrong[pos].timer.Stop() + data.wrong = data.wrong[:pos+copy(data.wrong[pos:], data.wrong[pos+1:])] + } + } + + if data.correct == nil && len(data.wrong) == 0 { + delete(passwords, username) + } +}