-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathauthn.go
142 lines (126 loc) · 3.49 KB
/
authn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// authn is a Kubernetes webhook token authentication service for LDAP authentication.
//
// Usage:
//
// authn ip key cert
//
// Arguments:
//
// ip: IP address of the LDAP directory
// key: private key for serving HTTPS
// cert: certificate for serving HTTPS
//
// You can create a private ky and self-signed certificate with:
//
// openssl req -x509 -newkey rsa:2048 -nodes -subj "/CN=localhost" -keyout key.pem -out cert.pem
//
package main
import (
"encoding/json"
"fmt"
"github.com/go-ldap/ldap"
"io/ioutil"
"k8s.io/api/authentication/v1"
"log"
"net/http"
"os"
"strings"
)
var ldapURL string
func main() {
ldapURL = "ldap://" + os.Args[1]
log.Printf("Using LDAP directory %s\n", ldapURL)
log.Println("Listening on port 443 for requests...")
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServeTLS(":443", os.Args[3], os.Args[2], nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
// Read body of POST request
b, err := ioutil.ReadAll(r.Body)
if err != nil {
writeError(w, err)
return
}
log.Printf("Receiving: %s\n", string(b))
// Unmarshal JSON from POST request to TokenReview object
// TokenReview: https://github.com/kubernetes/api/blob/master/authentication/v1/types.go
var tr v1.TokenReview
err = json.Unmarshal(b, &tr)
if err != nil {
writeError(w, err)
return
}
// Extract username and password from the token in the TokenReview object
s := strings.SplitN(tr.Spec.Token, ":", 2)
if len(s) != 2 {
writeError(w, fmt.Errorf("badly formatted token: %s", tr.Spec.Token))
return
}
username, password := s[0], s[1]
// Make LDAP Search request with extracted username and password
userInfo, err := ldapSearch(username, password)
if err != nil {
writeError(w, fmt.Errorf("failed LDAP Search request: %v", err))
return
}
// Set status of TokenReview object
if userInfo == nil {
tr.Status.Authenticated = false
} else {
tr.Status.Authenticated = true
tr.Status.User = *userInfo
}
// Marshal the TokenReview to JSON and send it back
b, err = json.Marshal(tr)
if err != nil {
writeError(w, err)
return
}
w.Write(b)
log.Printf("Returning: %s\n", string(b))
}
func writeError(w http.ResponseWriter, err error) {
err = fmt.Errorf("Error: %v", err)
w.WriteHeader(http.StatusInternalServerError) // 500
fmt.Fprintln(w, err)
log.Println(err)
}
func ldapSearch(username, password string) (*v1.UserInfo, error) {
// Connect to LDAP directory
l, err := ldap.DialURL(ldapURL)
if err != nil {
return nil, err
}
defer l.Close()
// Authenticate as LDAP admin user
err = l.Bind("cn=admin,dc=mycompany,dc=com", "adminpassword")
if err != nil {
return nil, err
}
// Execute LDAP Search request
searchRequest := ldap.NewSearchRequest(
"dc=mycompany,dc=com", // Search base
ldap.ScopeWholeSubtree, // Search scope
ldap.NeverDerefAliases, // Dereference aliases
0, // Size limit (0 = no limit)
0, // Time limit (0 = no limit)
false, // Types only
fmt.Sprintf("(&(objectClass=inetOrgPerson)(cn=%s)(userPassword=%s))", username, password), // Filter
nil, // Attributes (nil = all user attributes)
nil, // Additional 'Controls'
)
result, err := l.Search(searchRequest)
if err != nil {
return nil, err
}
// If LDAP Search produced a result, return UserInfo, otherwise, return nil
if len(result.Entries) == 0 {
return nil, nil
} else {
return &v1.UserInfo{
Username: username,
UID: username,
Groups: result.Entries[0].GetAttributeValues("ou"),
}, nil
}
}