Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8c9485202c |
10
Dockerfile
10
Dockerfile
|
@ -2,15 +2,13 @@ FROM golang:alpine
|
|||
|
||||
COPY . /go/src/github.com/tiagoapimenta/nginx-ldap-auth
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
RUN cd /go/src/github.com/tiagoapimenta/nginx-ldap-auth && \
|
||||
apk add --no-cache git && \
|
||||
go get -u gopkg.in/yaml.v2 && \
|
||||
go get -u gopkg.in/ldap.v2 && \
|
||||
go build -a -x -ldflags='-s -w -extldflags -static' -v -o /go/bin/nginx-ldap-auth ./main
|
||||
go build -ldflags='-s -w' -v -o /go/bin/nginx-ldap-auth ./main
|
||||
|
||||
FROM scratch
|
||||
FROM alpine
|
||||
|
||||
MAINTAINER Tiago A. Pimenta <tiagoapimenta@gmail.com>
|
||||
|
||||
|
@ -22,6 +20,6 @@ VOLUME /etc/nginx-ldap-auth
|
|||
|
||||
EXPOSE 5555
|
||||
|
||||
USER 65534:65534
|
||||
USER nobody
|
||||
|
||||
CMD [ "/usr/local/bin/nginx-ldap-auth", "--config", "/etc/nginx-ldap-auth/config.yaml" ]
|
||||
CMD [ "nginx-ldap-auth", "--config", "/etc/nginx-ldap-auth/config.yaml" ]
|
||||
|
|
|
@ -6,10 +6,6 @@ Use this in order to provide a ingress authentication over LDAP for Kubernetes,
|
|||
|
||||
kubectl apply -f k8s.yaml
|
||||
|
||||
For RBAC enabled cluster use the k8s-rbac.yaml manifest instead:
|
||||
|
||||
kubectl apply -f k8s-rbac.yaml
|
||||
|
||||
Configure your ingress with annotation `nginx.ingress.kubernetes.io/auth-url: http://nginx-ldap-auth.default.svc.cluster.local:5555` as described on [nginx documentation](https://kubernetes.github.io/ingress-nginx/examples/auth/external-auth/).
|
||||
|
||||
## Configuration
|
||||
|
|
2
build
2
build
|
@ -3,7 +3,7 @@
|
|||
set -e
|
||||
|
||||
base='docker.io/tpimenta/nginx-ldap-auth'
|
||||
version='v1.0.5'
|
||||
version='v1.1.0'
|
||||
image="$base:$version"
|
||||
|
||||
atexit() {
|
||||
|
|
|
@ -1,21 +1,50 @@
|
|||
web: 0.0.0.0:5555
|
||||
path: /
|
||||
message: "LDAP Login"
|
||||
servers:
|
||||
- ldaps://ldap1.example.com:636
|
||||
- ldaps://ldap2.example.com:636
|
||||
- ldaps://ldap3.example.com:636
|
||||
auth:
|
||||
bindDN: uid=seviceaccount,cn=users,dc=example,dc=com
|
||||
bindDN: cn=seviceaccount,cn=users,o=company
|
||||
bindPW: password
|
||||
user:
|
||||
baseDN: ou=users,dc=example,dc=com
|
||||
baseDN: ou=users,o=company
|
||||
filter: "(cn={0})"
|
||||
requiredGroups:
|
||||
- appAdmin
|
||||
attr: cn
|
||||
group:
|
||||
baseDN: ou=groups,dc=example,dc=com
|
||||
groupAttr: cn
|
||||
baseDN: ou=groups,o=company
|
||||
filter: "(member={0})"
|
||||
attr: cn
|
||||
timeout:
|
||||
success: 24h
|
||||
group: 24h
|
||||
wrong: 5m
|
||||
rules:
|
||||
- match:
|
||||
- header: X-Sent-From
|
||||
value: nginx-ingress-controller
|
||||
- header: X-Auth-Request-Redirect
|
||||
regex: "^/dashboard"
|
||||
allow:
|
||||
- group: SysAdmin
|
||||
- group: AppAdmin
|
||||
- group: Operator
|
||||
- user: Jhon
|
||||
deny:
|
||||
- group: Guest
|
||||
- match:
|
||||
- header: X-Sent-From
|
||||
value: nginx-ingress-controller
|
||||
- header: X-Original-Method
|
||||
value: GET
|
||||
- header: X-Original-URL
|
||||
regex: "^https?://server.domain/"
|
||||
allow:
|
||||
- group: Guest
|
||||
- match:
|
||||
- header: X-Sent-From
|
||||
value: nginx-ingress-controller
|
||||
- header: X-Auth-Request-Redirect
|
||||
regex: /login
|
||||
allowAnonymous: true
|
||||
|
|
|
@ -4,8 +4,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/tiagoapimenta/nginx-ldap-auth/ldap"
|
||||
|
||||
gldap "gopkg.in/ldap.v2"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
|
@ -25,8 +23,6 @@ func NewService(pool *ldap.Pool, base, filter, attr string) *Service {
|
|||
}
|
||||
|
||||
func (p *Service) Find(id string) ([]string, error) {
|
||||
id = gldap.EscapeFilter(id)
|
||||
|
||||
ok, _, groups, err := p.pool.Search(
|
||||
p.base,
|
||||
strings.Replace(p.filter, "{0}", id, -1),
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
resourceNames:
|
||||
- "nginx-ldap-auth"
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
resourceNames:
|
||||
- "nginx-ldap-auth"
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: nginx-ldap-auth
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: nginx-ldap-auth
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: nginx-ldap-auth
|
||||
port: 5555
|
||||
protocol: TCP
|
||||
targetPort: 5555
|
||||
selector:
|
||||
app: nginx-ldap-auth
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
labels:
|
||||
app: nginx-ldap-auth
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-ldap-auth
|
||||
spec:
|
||||
serviceAccountName: nginx-ldap-auth
|
||||
containers:
|
||||
- image: docker.io/tpimenta/nginx-ldap-auth:v1.0.5
|
||||
name: nginx-ldap-auth
|
||||
command:
|
||||
- "/usr/local/bin/nginx-ldap-auth"
|
||||
- "--config"
|
||||
- "/etc/nginx-ldap-auth/config.yaml"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 5555
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/nginx-ldap-auth
|
||||
volumes:
|
||||
- name: config
|
||||
secret:
|
||||
secretName: nginx-ldap-auth
|
||||
items:
|
||||
- key: config.yaml
|
||||
path: config.yaml
|
48
k8s.yaml
48
k8s.yaml
|
@ -1,3 +1,42 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
resourceNames:
|
||||
- "nginx-ldap-auth"
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
resourceNames:
|
||||
- "nginx-ldap-auth"
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: nginx-ldap-auth
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: nginx-ldap-auth
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: nginx-ldap-auth
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
|
@ -25,13 +64,14 @@ spec:
|
|||
labels:
|
||||
app: nginx-ldap-auth
|
||||
spec:
|
||||
serviceAccountName: nginx-ldap-auth
|
||||
containers:
|
||||
- image: docker.io/tpimenta/nginx-ldap-auth:v1.0.5
|
||||
- image: docker.io/tpimenta/nginx-ldap-auth:v1.1.0
|
||||
name: nginx-ldap-auth
|
||||
command:
|
||||
- "/usr/local/bin/nginx-ldap-auth"
|
||||
- "--config"
|
||||
- "/etc/nginx-ldap-auth/config.yaml"
|
||||
- "nginx-ldap-auth"
|
||||
- "--config"
|
||||
- "/etc/nginx-ldap-auth/config.yaml"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 5555
|
||||
|
|
|
@ -19,7 +19,7 @@ func (p *Pool) Connect() error {
|
|||
}
|
||||
|
||||
if p.conn != nil {
|
||||
p.conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
address := fmt.Sprintf("%s:%d", p.url, p.port)
|
||||
|
@ -37,11 +37,6 @@ func (p *Pool) Connect() error {
|
|||
err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
log.Printf("It was not possble to start TLS, falling back to plain: %v.\n", err)
|
||||
conn.Close()
|
||||
conn, err = ldap.Dial("tcp", address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.conn = conn
|
||||
}
|
||||
|
|
23
ldap/jail.go
23
ldap/jail.go
|
@ -1,23 +0,0 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
ldap "gopkg.in/ldap.v2"
|
||||
)
|
||||
|
||||
func (p *Pool) networkJail(f func() error) (bool, error) {
|
||||
err := f()
|
||||
if err != nil && ldap.IsErrorWithCode(err, ldap.ErrorNetwork) {
|
||||
log.Printf("Network problem, trying to reconnect once: %v.\n", err)
|
||||
err = p.Connect()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = f()
|
||||
if err != nil && ldap.IsErrorWithCode(err, ldap.ErrorNetwork) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, err
|
||||
}
|
|
@ -10,13 +10,7 @@ func (p *Pool) Validate(username, password string) (bool, error) {
|
|||
}
|
||||
|
||||
p.admin = false
|
||||
var ok bool
|
||||
ok, err = p.networkJail(func() error {
|
||||
return p.conn.Bind(username, password)
|
||||
})
|
||||
if !ok {
|
||||
return false, err
|
||||
}
|
||||
err = p.conn.Bind(username, password)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
@ -34,9 +28,7 @@ func (p *Pool) auth() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
_, err := p.networkJail(func() error {
|
||||
return p.conn.Bind(p.username, p.password)
|
||||
})
|
||||
err := p.conn.Bind(p.username, p.password)
|
||||
if err == nil {
|
||||
p.admin = true
|
||||
}
|
||||
|
|
|
@ -21,27 +21,22 @@ func (p *Pool) Search(base, filter string, attr string) (bool, string, []string,
|
|||
list = []string{attr}
|
||||
}
|
||||
|
||||
var res *ldap.SearchResult
|
||||
_, err = p.networkJail(func() error {
|
||||
res, err = p.conn.Search(ldap.NewSearchRequest(
|
||||
base,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
filter,
|
||||
list,
|
||||
nil,
|
||||
))
|
||||
return err
|
||||
})
|
||||
|
||||
res, err := p.conn.Search(ldap.NewSearchRequest(
|
||||
base,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
filter,
|
||||
list,
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
return false, "", nil, err
|
||||
}
|
||||
|
||||
if res == nil || len(res.Entries) == 0 {
|
||||
if len(res.Entries) == 0 {
|
||||
return true, "", nil, fmt.Errorf("No results for %s filter %s", base, filter)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,16 +7,10 @@ type AuthConfig struct {
|
|||
BindPW string `yaml:"bindPW"`
|
||||
}
|
||||
|
||||
type UserConfig struct {
|
||||
BaseDN string `yaml:"baseDN"`
|
||||
Filter string `yaml:"filter"`
|
||||
RequiredGroups []string `yaml:"requiredGroups"`
|
||||
}
|
||||
|
||||
type GroupConfig struct {
|
||||
BaseDN string `yaml:"baseDN"`
|
||||
GroupAttr string `yaml:"groupAttr"`
|
||||
Filter string `yaml:"filter"`
|
||||
type SearchConfig struct {
|
||||
BaseDN string `yaml:"baseDN"`
|
||||
Filter string `yaml:"filter"`
|
||||
Attr string `yaml:"attr"`
|
||||
}
|
||||
|
||||
type TimeoutConfig struct {
|
||||
|
@ -24,13 +18,32 @@ type TimeoutConfig struct {
|
|||
Wrong time.Duration `yaml:"wrong"`
|
||||
}
|
||||
|
||||
type MatchConfig struct {
|
||||
Header string `yaml:"header"`
|
||||
Value string `yaml:"value"`
|
||||
Regex string `yaml:"regex"`
|
||||
}
|
||||
|
||||
type PermissionConfig struct {
|
||||
Group string `yaml:"group"`
|
||||
User string `yaml:"user"`
|
||||
}
|
||||
|
||||
type RulesConfig struct {
|
||||
Match []MatchConfig `yaml:"match"`
|
||||
Allow []PermissionConfig `yaml:"allow"`
|
||||
Deny []PermissionConfig `yaml:"deny"`
|
||||
AllowAnonymous bool `yaml:"allowAnonymous"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Web string `yaml:"web"`
|
||||
Path string `yaml:"path"`
|
||||
Message string `yaml:"message"`
|
||||
Servers []string `yaml:"servers"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
User UserConfig `yaml:"user"`
|
||||
Group GroupConfig `yaml:"group"`
|
||||
User SearchConfig `yaml:"user"`
|
||||
Group SearchConfig `yaml:"group"`
|
||||
Timeout TimeoutConfig `yaml:"timeout"`
|
||||
Rules []RulesConfig `yaml:"rules"`
|
||||
}
|
||||
|
|
|
@ -23,12 +23,13 @@ func parseConfig() (string, *Config, error) {
|
|||
Web: "0.0.0.0:5555",
|
||||
Path: "/",
|
||||
Message: "LDAP Login",
|
||||
User: UserConfig{
|
||||
User: SearchConfig{
|
||||
Filter: "(cn={0})",
|
||||
Attr: "cn",
|
||||
},
|
||||
Group: GroupConfig{
|
||||
Filter: "(member={0})",
|
||||
GroupAttr: "cn",
|
||||
Group: SearchConfig{
|
||||
Filter: "(member={0})",
|
||||
Attr: "cn",
|
||||
},
|
||||
Timeout: TimeoutConfig{
|
||||
Success: 24 * time.Hour,
|
||||
|
|
|
@ -33,7 +33,7 @@ func (p *Service) Validate(username, password string) bool {
|
|||
|
||||
ok, err := p.validate(username, password)
|
||||
if err != nil {
|
||||
log.Printf("Could not validate user %s: %v\n", username, err)
|
||||
log.Printf("Could not validade user %s: %v\n", username, err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ func (p *Service) validate(username, password string) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
if !ok || err != nil || p.required == nil || len(p.required) == 0 {
|
||||
if ok || p.required == nil || len(p.required) == 0 {
|
||||
return err == nil, nil
|
||||
}
|
||||
|
||||
|
|
44
test-server
44
test-server
|
@ -1,44 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
for name in ldap-test-server ldap-test-client; do
|
||||
if docker ps -a --format '{{.Names}}' | egrep -q "^${name}\$"; then
|
||||
docker rm -f "$name" || :
|
||||
fi
|
||||
done
|
||||
|
||||
docker run \
|
||||
-p 389:389 \
|
||||
-p 636:636 \
|
||||
--name ldap-test-server \
|
||||
-d \
|
||||
osixia/openldap:1.2.2
|
||||
|
||||
# docker exec ldap-test-server ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin
|
||||
|
||||
cat > /tmp/config.yaml <<EOF
|
||||
web: 0.0.0.0:5555
|
||||
path: /
|
||||
servers:
|
||||
- ldap://$(hostname -I | cut -d\ -f1)
|
||||
auth:
|
||||
bindDN: cn=admin,dc=example,dc=org
|
||||
bindPW: admin
|
||||
user:
|
||||
baseDN: dc=example,dc=org
|
||||
filter: "(cn={0})"
|
||||
EOF
|
||||
|
||||
while ! nc -z -w5 127.0.0.1 389; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
sleep 2
|
||||
|
||||
docker run \
|
||||
-p 5555:5555 \
|
||||
-v '/tmp/config.yaml:/etc/nginx-ldap-auth/config.yaml:ro' \
|
||||
--name ldap-test-client \
|
||||
-d \
|
||||
docker.io/tpimenta/nginx-ldap-auth:v1.0.4
|
|
@ -4,8 +4,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/tiagoapimenta/nginx-ldap-auth/ldap"
|
||||
|
||||
gldap "gopkg.in/ldap.v2"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
|
@ -23,8 +21,6 @@ func NewService(pool *ldap.Pool, base, filter string) *Service {
|
|||
}
|
||||
|
||||
func (p *Service) Find(username string) (bool, string, error) {
|
||||
username = gldap.EscapeFilter(username)
|
||||
|
||||
ok, id, _, err := p.pool.Search(
|
||||
p.base,
|
||||
strings.Replace(p.filter, "{0}", username, -1),
|
||||
|
|
Reference in a new issue