Skip to content

Commit 809a38d

Browse files
Merge branch 'main' into mtls
2 parents cbd76e2 + 39d6404 commit 809a38d

File tree

14 files changed

+1042
-54
lines changed

14 files changed

+1042
-54
lines changed

.github/workflows/docker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Install Go
1919
uses: actions/setup-go@v2
2020
with:
21-
go-version: "1.16.x"
21+
go-version: "1.19.x"
2222

2323
- name: Checkout code
2424
uses: actions/checkout@v2

.github/workflows/go_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ jobs:
44
test:
55
strategy:
66
matrix:
7-
go-version: [1.17.x, 1.18.x]
7+
go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x]
88
os: [ubuntu-latest]
99
runs-on: ${{ matrix.os }}
1010
steps:

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ While performing simple user authentication is pretty straightforward, performin
88
Docker Registry 2.0 introduced a new, token-based authentication and authorization protocol, but the server to generate them was not released.
99
Thus, most guides found on the internet still describe a set up with a reverse proxy performing access control.
1010

11-
This server fills the gap and implements the protocol described [here](https://github.com/docker/distribution/blob/master/docs/spec/auth/token.md).
11+
This server fills the gap and implements the protocol described [here](https://github.com/docker/distribution/blob/main/docs/spec/auth/token.md).
1212

1313
Supported authentication methods:
1414
* Static list of users
15-
* Google Sign-In (incl. Google for Work / GApps for domain) (documented [here](https://github.com/cesanta/docker_auth/blob/master/examples/reference.yml))
15+
* Google Sign-In (incl. Google for Work / GApps for domain) (documented [here](https://github.com/cesanta/docker_auth/blob/main/examples/reference.yml))
1616
* [Github Sign-In](docs/auth-methods.md#github)
1717
* Gitlab Sign-In
1818
* LDAP bind ([demo](https://github.com/kwk/docker-registry-setup))
1919
* MongoDB user collection
2020
* MySQL/MariaDB, PostgreSQL, SQLite database table
21-
* [External program](https://github.com/cesanta/docker_auth/blob/master/examples/ext_auth.sh)
21+
* [External program](https://github.com/cesanta/docker_auth/blob/main/examples/ext_auth.sh)
2222

2323
Supported authorization methods:
2424
* Static ACL
@@ -55,7 +55,7 @@ $ docker run \
5555
cesanta/docker_auth:1 /config/auth_config.yml
5656
```
5757

58-
See the [example config files](https://github.com/cesanta/docker_auth/tree/master/examples/) to get an idea of what is possible.
58+
See the [example config files](https://github.com/cesanta/docker_auth/tree/main/examples/) to get an idea of what is possible.
5959

6060
## Troubleshooting
6161

auth_server/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.17-alpine3.14 as build
1+
FROM golang:1.20-alpine3.17 as build
22

33
ARG VERSION
44
ENV VERSION "${VERSION}"
@@ -12,7 +12,7 @@ COPY . /build
1212
WORKDIR /build
1313
RUN make build
1414

15-
FROM alpine:3.14
15+
FROM alpine:3.17
1616
COPY --from=build /build/auth_server /docker_auth/
1717
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
1818
ENTRYPOINT ["/docker_auth/auth_server"]

auth_server/authn/data/oidc_auth.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<body>
1010
<div id="panel">
1111
<p>
12-
<a id="login-with-oidc" href="{{.AuthEndpoint}}?response_type=code&scope=openid%20email&client_id={{.ClientId}}&redirect_uri={{.RedirectURI}}">
12+
<a id="login-with-oidc" href="{{.AuthEndpoint}}?response_type=code&scope={{.Scope}}&client_id={{.ClientId}}&redirect_uri={{.RedirectURI}}">
1313
Login with OIDC Provider
1414
</a>
1515
</p>

auth_server/authn/ldap_auth.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
type LabelMap struct {
3333
Attribute string `yaml:"attribute,omitempty"`
3434
ParseCN bool `yaml:"parse_cn,omitempty"`
35+
LowerCase bool `yaml:"lower_case",omitempty"`
3536
}
3637

3738
type LDAPAuthConfig struct {
@@ -299,6 +300,11 @@ func (la *LDAPAuth) getLabelsFromMap(attrMap map[string][]string) (map[string][]
299300
mappingValues[i] = cn
300301
}
301302
}
303+
if mapping.LowerCase {
304+
for i, value := range mappingValues {
305+
mappingValues[i] = strings.ToLower(value)
306+
}
307+
}
302308
labels[key] = mappingValues
303309
}
304310
}

auth_server/authn/oidc_auth.go

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ import (
2121
"encoding/json"
2222
"errors"
2323
"fmt"
24-
"golang.org/x/oauth2"
2524
"html/template"
2625
"io/ioutil"
2726
"net/http"
2827
"strings"
2928
"time"
3029

30+
"golang.org/x/oauth2"
31+
3132
"github.com/coreos/go-oidc/v3/oidc"
3233

3334
"github.com/cesanta/glog"
@@ -52,6 +53,14 @@ type OIDCAuthConfig struct {
5253
HTTPTimeout int `yaml:"http_timeout,omitempty"`
5354
// the URL of the docker registry. Used to generate a full docker login command after authentication
5455
RegistryURL string `yaml:"registry_url,omitempty"`
56+
// --- optional ---
57+
// String claim to use for the username
58+
UserClaim string `yaml:"user_claim,omitempty"`
59+
// --- optional ---
60+
// []string to add as labels.
61+
LabelsClaims []string `yaml:"labels_claims,omitempty"`
62+
// --- optional ---
63+
Scopes []string `yaml:"scopes,omitempty"`
5564
}
5665

5766
// OIDCRefreshTokenResponse is sent by OIDC provider in response to the grant_type=refresh_token request.
@@ -66,14 +75,6 @@ type OIDCRefreshTokenResponse struct {
6675
ErrorDescription string `json:"error_description,omitempty"`
6776
}
6877

69-
// ProfileResponse is sent by the /userinfo endpoint or contained in the ID token.
70-
// We use it to validate access token and (re)verify the email address associated with it.
71-
type OIDCProfileResponse struct {
72-
Email string `json:"email,omitempty"`
73-
VerifiedEmail bool `json:"verified_email,omitempty"`
74-
// There are more fields, but we only need email.
75-
}
76-
7778
// The specific OIDC authenticator
7879
type OIDCAuth struct {
7980
config *OIDCAuthConfig
@@ -109,7 +110,7 @@ func NewOIDCAuth(c *OIDCAuthConfig) (*OIDCAuth, error) {
109110
ClientSecret: c.ClientSecret,
110111
Endpoint: prov.Endpoint(),
111112
RedirectURL: c.RedirectURL,
112-
Scopes: []string{oidc.ScopeOpenID, "email"},
113+
Scopes: c.Scopes,
113114
}
114115
return &OIDCAuth{
115116
config: c,
@@ -144,11 +145,12 @@ Executes tmpl for the OIDC login page.
144145
*/
145146
func (ga *OIDCAuth) doOIDCAuthPage(rw http.ResponseWriter) {
146147
if err := ga.tmpl.Execute(rw, struct {
147-
AuthEndpoint, RedirectURI, ClientId string
148+
AuthEndpoint, RedirectURI, ClientId, Scope string
148149
}{
149150
AuthEndpoint: ga.provider.Endpoint().AuthURL,
150151
RedirectURI: ga.oauth.RedirectURL,
151152
ClientId: ga.oauth.ClientID,
153+
Scope: strings.Join(ga.config.Scopes, " "),
152154
}); err != nil {
153155
http.Error(rw, fmt.Sprintf("Template error: %s", err), http.StatusInternalServerError)
154156
}
@@ -191,32 +193,47 @@ func (ga *OIDCAuth) doOIDCAuthCreateToken(rw http.ResponseWriter, code string) {
191193
http.Error(rw, fmt.Sprintf("Failed to verify ID token: %s", err), http.StatusInternalServerError)
192194
return
193195
}
194-
var prof OIDCProfileResponse
195-
if err := idTok.Claims(&prof); err != nil {
196-
http.Error(rw, fmt.Sprintf("Failed to get mail information from ID token: %s", err), http.StatusInternalServerError)
196+
var claims map[string]interface{}
197+
if err := idTok.Claims(&claims); err != nil {
198+
http.Error(rw, fmt.Sprintf("Failed to get claims from ID token: %s", err), http.StatusInternalServerError)
197199
return
198200
}
199-
if prof.Email == "" {
200-
http.Error(rw, fmt.Sprintf("No mail information given in ID token"), http.StatusInternalServerError)
201+
username, _ := claims[ga.config.UserClaim].(string)
202+
if username == "" {
203+
http.Error(rw, fmt.Sprintf("No %q claim in ID token", ga.config.UserClaim), http.StatusInternalServerError)
201204
return
202205
}
203206

204-
glog.V(2).Infof("New OIDC auth token for %s (Current time: %s, expiration time: %s)", prof.Email, time.Now().String(), tok.Expiry.String())
207+
glog.V(2).Infof("New OIDC auth token for %s (Current time: %s, expiration time: %s)", username, time.Now().String(), tok.Expiry.String())
205208

206209
dbVal := &TokenDBValue{
207210
TokenType: tok.TokenType,
208211
AccessToken: tok.AccessToken,
209212
RefreshToken: tok.RefreshToken,
210213
ValidUntil: tok.Expiry.Add(time.Duration(-30) * time.Second),
214+
Labels: ga.getLabels(claims),
211215
}
212-
dp, err := ga.db.StoreToken(prof.Email, dbVal, true)
216+
dp, err := ga.db.StoreToken(username, dbVal, true)
213217
if err != nil {
214218
glog.Errorf("Failed to record server token: %s", err)
215219
http.Error(rw, "Failed to record server token: %s", http.StatusInternalServerError)
216220
return
217221
}
218222

219-
ga.doOIDCAuthResultPage(rw, prof.Email, dp)
223+
ga.doOIDCAuthResultPage(rw, username, dp)
224+
}
225+
226+
func (ga *OIDCAuth) getLabels(claims map[string]interface{}) api.Labels {
227+
labels := make(api.Labels, len(ga.config.LabelsClaims))
228+
for _, claim := range ga.config.LabelsClaims {
229+
values, _ := claims[claim].([]interface{})
230+
for _, v := range values {
231+
if str, _ := v.(string); str != "" {
232+
labels[claim] = append(labels[claim], str)
233+
}
234+
}
235+
}
236+
return labels
220237
}
221238

222239
/*
@@ -294,8 +311,15 @@ func (ga *OIDCAuth) validateServerToken(user string) (*TokenDBValue, error) {
294311
glog.Warningf("Token for %q failed validation: %s", user, err)
295312
return nil, fmt.Errorf("server token invalid: %s", err)
296313
}
297-
if tokUser.Email != user {
298-
glog.Errorf("token for wrong user: expected %s, found %s", user, tokUser.Email)
314+
315+
var claims map[string]interface{}
316+
if err := tokUser.Claims(&claims); err != nil {
317+
glog.Errorf("error retrieving claims: %v", err)
318+
return nil, fmt.Errorf("error retrieving claims: %w", err)
319+
}
320+
claimUsername, _ := claims[ga.config.UserClaim].(string)
321+
if claimUsername != user {
322+
glog.Errorf("token for wrong user: expected %s, found %s", user, claimUsername)
299323
return nil, fmt.Errorf("found token for wrong user")
300324
}
301325
texp := v.ValidUntil.Sub(time.Now())
@@ -335,7 +359,15 @@ func (ga *OIDCAuth) Authenticate(user string, password api.PasswordString) (bool
335359
} else if err != nil {
336360
return false, nil, err
337361
}
338-
return true, nil, nil
362+
363+
v, err := ga.db.GetValue(user)
364+
if err != nil || v == nil {
365+
if err == nil {
366+
err = errors.New("no db value, please sign out and sign in again")
367+
}
368+
return false, nil, err
369+
}
370+
return true, v.Labels, err
339371
}
340372

341373
func (ga *OIDCAuth) Stop() {

auth_server/go.mod

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,58 @@ module github.com/cesanta/docker_auth/auth_server
33
go 1.16
44

55
require (
6-
cloud.google.com/go v0.78.0 // indirect
7-
cloud.google.com/go/storage v1.14.0
8-
github.com/casbin/casbin/v2 v2.24.0
6+
cloud.google.com/go/compute v1.10.0 // indirect
7+
cloud.google.com/go/iam v0.5.0 // indirect
8+
cloud.google.com/go/storage v1.27.0
9+
github.com/PuerkitoBio/goquery v1.5.1 // indirect
10+
github.com/casbin/casbin/v2 v2.55.1
911
github.com/cesanta/glog v0.0.0-20150527111657-22eb27a0ae19
10-
github.com/coreos/go-oidc/v3 v3.0.0
11-
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5
12-
github.com/deckarep/golang-set v1.7.1
13-
github.com/docker/distribution v2.8.0+incompatible
12+
github.com/coreos/go-oidc/v3 v3.4.0
13+
github.com/dchest/uniuri v0.0.0-20220929095258-3027df40b6ce
14+
github.com/deckarep/golang-set v1.8.0
15+
github.com/docker/distribution v2.8.1+incompatible
1416
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
1517
github.com/go-ldap/ldap v3.0.3+incompatible
1618
github.com/go-redis/redis v6.15.9+incompatible
17-
github.com/go-sql-driver/mysql v1.5.0
18-
github.com/golang/snappy v0.0.3 // indirect
19+
github.com/go-sql-driver/mysql v1.6.0
20+
github.com/go-stack/stack v1.8.1 // indirect
21+
github.com/gobuffalo/genny v0.1.1 // indirect
22+
github.com/gobuffalo/gogen v0.1.1 // indirect
23+
github.com/goccy/go-json v0.9.11 // indirect
24+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
25+
github.com/google/go-cmp v0.5.9 // indirect
26+
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
1927
github.com/gorilla/mux v1.8.0 // indirect
28+
github.com/jstemmer/go-junit-report v1.0.0 // indirect
29+
github.com/karrick/godirwalk v1.10.3 // indirect
30+
github.com/klauspost/compress v1.15.11 // indirect
2031
github.com/kr/text v0.2.0 // indirect
21-
github.com/lib/pq v1.9.0
22-
github.com/magefile/mage v1.11.0 // indirect
32+
github.com/lib/pq v1.10.7
33+
github.com/magefile/mage v1.14.0 // indirect
2334
github.com/mattn/go-sqlite3 v2.0.3+incompatible
35+
github.com/montanaflynn/stats v0.6.6 // indirect
36+
github.com/pelletier/go-toml v1.7.0 // indirect
2437
github.com/schwarmco/go-cartesian-product v0.0.0-20180515110546-d5ee747a6dc9
25-
github.com/sirupsen/logrus v1.8.0 // indirect
38+
github.com/sirupsen/logrus v1.9.0 // indirect
2639
github.com/stretchr/testify v1.7.0 // indirect
2740
github.com/syndtr/goleveldb v1.0.0
28-
go.mongodb.org/mongo-driver v1.7.1
41+
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
42+
go.mongodb.org/mongo-driver v1.10.2
2943
go.opencensus.io v0.23.0 // indirect
30-
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
31-
golang.org/x/net v0.0.0-20210326060303-6b1517762897
32-
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93
33-
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 // indirect
34-
google.golang.org/api v0.40.0
35-
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb // indirect
36-
google.golang.org/grpc v1.36.0 // indirect
44+
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
45+
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b
46+
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
47+
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
48+
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
49+
golang.org/x/tools v0.1.12 // indirect
50+
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
51+
google.golang.org/api v0.98.0
52+
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect
3753
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
3854
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
3955
gopkg.in/fsnotify.v1 v1.4.7
4056
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
4157
gopkg.in/yaml.v2 v2.4.0
42-
xorm.io/builder v0.3.9 // indirect
43-
xorm.io/xorm v1.0.7
58+
xorm.io/builder v0.3.12 // indirect
59+
xorm.io/xorm v1.3.2
4460
)

0 commit comments

Comments
 (0)