Compare commits

..

1 commit
master ... next

Author SHA1 Message Date
Tiago Augusto Pimenta 8c9485202c Draft 2018-10-17 21:37:22 -03:00
16 changed files with 131 additions and 234 deletions

View file

@ -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" ]

View file

@ -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
View file

@ -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() {

View file

@ -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

View file

@ -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),

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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"`
}

View file

@ -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,

View file

@ -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
}

View file

@ -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

View file

@ -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),