Skip to content

Commit 3bfe107

Browse files
authored
feat: add token authentication with keycloak (#167)
2 parents 0f54555 + 1ca1a1a commit 3bfe107

27 files changed

+641
-53
lines changed
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
data/registry/
2+
data/keycloak/
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Token Authentication with Keycloak
2+
3+
In this example, we'll see how to configure your keycloak server and use token authentication with your registry. This will use the [docker registry v2 token authentication protocol](https://docs.docker.com/registry/spec/auth/token/).
4+
5+
![token protocol](https://docs.docker.com/registry/spec/images/v2-registry-auth.png)
6+
7+
In this image, we will replace the docker client/daemon by the Docker Registry UI. Here are the steps:
8+
9+
1. Attempt to get a resource (catalog, image info, image delete) with the registry.
10+
2. If the registry requires authorization it will return a `401 Unauthorized` HTTP response with information on how to authenticate.
11+
3. The **docker registry ui** makes a request to **keycloak** for a Bearer token.
12+
1. Your browser will use the [Basic Access Authentication Protocol](https://en.wikipedia.org/wiki/Basic_access_authentication#Protocol). But keycloak does not support this protocol... That's why we need a nginx proxy on top of keycloak.
13+
2. Your proxy will receive a request on `/auth/realms/{realm name}/protocol/docker-v2/auth` without `Authentication` header. It will return a `401 Unauthorized` HTTP response with `WWW-Authenticate` header.
14+
3. Your browser will ask you your credentials.
15+
4. The proxy will pass the credentials to keycloak.
16+
4. Keycloak returns an opaque Bearer token representing the client’s authorized access.
17+
5. The **docker registry ui** retries the original request with the Bearer token embedded in the request’s Authorization header.
18+
6. The Registry authorizes the client by validating the Bearer token and the claim set embedded within it and begins the session as usual.
19+
20+
:warning: If you are configuring from scratch your own keycloak server, remove files in `data` folder first with certificates in `conf/registry/localhost.*`
21+
22+
## Configure your nginx/proxy server
23+
24+
I will highlight required configuration for Basic Access Authentication Protocol. Replace the `{realm name}` by the name of your realm. In my example the realm is master, but you should create your own realm for your users.
25+
26+
```nginx
27+
resolver 127.0.0.11 valid=30s;
28+
set $keycloak "http://keycloak:8080";
29+
30+
# Location to get keycloak token
31+
location /auth/realms/{realm name}/protocol/docker-v2/auth {
32+
proxy_set_header X-Forwarded-Proto $scheme;
33+
proxy_set_header Host $host;
34+
proxy_set_header X-Forwarded-Host $host;
35+
# By default, keycloak returns 400 instead of 401, we need to change that
36+
if ($http_authorization = "") {
37+
add_header WWW-Authenticate 'Basic realm="Keycloak login"' always;
38+
return 401;
39+
}
40+
proxy_pass $keycloak;
41+
}
42+
```
43+
44+
Start your nginx server. It will be available on http://localhost/ in my example.
45+
46+
```sh
47+
docker-compose up -d proxy
48+
```
49+
50+
## Configure your keycloak server
51+
52+
I will highlight required configuration for docker protocol. You will need to add this option to your keycloak command line:
53+
54+
```
55+
-Dkeycloak.profile.feature.docker=enabled
56+
```
57+
58+
Then the defalt user can be configured via environment variables
59+
```yml
60+
services:
61+
keycloak:
62+
image: jboss/keycloak
63+
environment:
64+
KEYCLOAK_USER: admin
65+
KEYCLOAK_PASSWORD: password
66+
user: root
67+
networks:
68+
- registry-ui-net
69+
command: -Dkeycloak.profile.feature.docker=enabled -b 0.0.0.0
70+
```
71+
72+
Now you can start your keycloak server, it will be available on http://localhost/auth in my example.
73+
74+
75+
```sh
76+
docker-compose up -d keycloak
77+
```
78+
79+
Now you need to configure your docker client with these steps:
80+
81+
Go to the keycloak home page: http://localhost/auth and click on `Administration Console`.
82+
83+
![keycloak-home](https://raw.github.com/Joxit/docker-registry-ui/main/examples/token-auth-keycloak/images/01-keycloak-home.png)
84+
85+
Sign in with your login and password (in my example it's `admin` and `password`).
86+
87+
![keycloak-home](https://raw.github.com/Joxit/docker-registry-ui/main/examples/token-auth-keycloak/images/02-keycloak-signin.png)
88+
89+
Go to `Clients` in the left side menu.
90+
91+
![keycloak-home](https://raw.github.com/Joxit/docker-registry-ui/main/examples/token-auth-keycloak/images/03-keycloak-to-clients.png)
92+
93+
Create a new client.
94+
95+
![keycloak-home](https://raw.github.com/Joxit/docker-registry-ui/main/examples/token-auth-keycloak/images/04-keycloak-create-client.png)
96+
97+
Enter a name for `Client ID`, choose `docker-v2` as the `Client Protocol`, and click `Save`.
98+
99+
![keycloak-home](https://raw.github.com/Joxit/docker-registry-ui/main/examples/token-auth-keycloak/images/05-keycloak-new-client.png)
100+
101+
Navigate to `Installation` tab, choose `Docker Compose YAML` as `Format Option` and click `Download`
102+
103+
![keycloak-home](https://raw.github.com/Joxit/docker-registry-ui/main/examples/token-auth-keycloak/images/06-keycloak-download.png)
104+
105+
When you extract the archive, the resulting directory should look like this.
106+
107+
```
108+
keycloak-docker-compose-yaml
109+
├── certs
110+
│ ├── localhost.crt
111+
│ ├── localhost.key
112+
│ └── localhost_trust_chain.pem
113+
├── data
114+
├── docker-compose.yaml
115+
└── README.md
116+
```
117+
118+
Copy all the files from `certs` folder to `conf/registry` (this will replace files generated for this example).
119+
120+
## Configure your registry server
121+
122+
The last step is the configuration of your registry server. The config file is located in `conf/registry/config.yml`. The import part of the configuration is `auth.token` where you need to set `realm`, `service`, `issuer` and the `rootcertbundle` from the previous archive.
123+
124+
```yml
125+
auth:
126+
token:
127+
realm: http://localhost/auth/realms/{realm name}/protocol/docker-v2/auth
128+
service: docker-registry
129+
issuer: http://localhost/auth/realms/{realm name}
130+
rootcertbundle: /etc/docker/registry/localhost_trust_chain.pem
131+
```
132+
133+
Now you can start your docker registry with your docker registry ui.
134+
135+
```sh
136+
docker-compose up -d registry ui
137+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
server {
2+
listen 80;
3+
server_name localhost;
4+
resolver 127.0.0.11 valid=30s;
5+
6+
set $keycloak "http://keycloak:8080";
7+
set $registry "http://registry:5000";
8+
set $ui "http://ui";
9+
10+
11+
#charset koi8-r;
12+
#access_log /var/log/nginx/host.access.log main;
13+
14+
# disable any limits to avoid HTTP 413 for large image uploads
15+
client_max_body_size 0;
16+
17+
# required to avoid HTTP 411: see Issue #1486 (https://github.com/moby/moby/issues/1486)
18+
chunked_transfer_encoding on;
19+
# required for strict SNI checking: see Issue #70 (https://github.com/Joxit/docker-registry-ui/issues/70)
20+
proxy_ssl_server_name on;
21+
proxy_buffering off;
22+
proxy_ignore_headers "X-Accel-Buffering";
23+
24+
location /v2 {
25+
# Do not allow connections from docker 1.5 and earlier
26+
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
27+
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
28+
return 404;
29+
}
30+
proxy_set_header X-Forwarded-Proto $scheme;
31+
proxy_set_header Host $host;
32+
proxy_set_header X-Forwarded-Host $host;
33+
proxy_pass $registry;
34+
}
35+
36+
location /auth {
37+
proxy_set_header X-Forwarded-Proto $scheme;
38+
proxy_set_header Host $host;
39+
proxy_set_header X-Forwarded-Host $host;
40+
proxy_pass $keycloak;
41+
}
42+
43+
location /auth/realms/master/protocol/docker-v2/auth {
44+
proxy_set_header X-Forwarded-Proto $scheme;
45+
proxy_set_header Host $host;
46+
proxy_set_header X-Forwarded-Host $host;
47+
if ($http_authorization = "") {
48+
add_header WWW-Authenticate 'Basic realm="Keycloak login"' always;
49+
return 401;
50+
}
51+
proxy_pass $keycloak;
52+
}
53+
54+
location /ui {
55+
proxy_pass $ui;
56+
}
57+
58+
location / {
59+
root /usr/share/nginx/html;
60+
index index.html index.htm;
61+
}
62+
63+
#error_page 404 /404.html;
64+
65+
# redirect server error pages to the static page /50x.html
66+
#
67+
error_page 500 502 503 504 /50x.html;
68+
location = /50x.html {
69+
root /usr/share/nginx/html;
70+
}
71+
72+
# deny access to .htaccess files, if Apache's document root
73+
# concurs with nginx's one
74+
#
75+
#location ~ /\.ht {
76+
# deny all;
77+
#}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: 0.1
2+
log:
3+
fields:
4+
service: registry
5+
storage:
6+
delete:
7+
enabled: true
8+
cache:
9+
blobdescriptor: inmemory
10+
filesystem:
11+
rootdirectory: /var/lib/registry
12+
http:
13+
addr: :5000
14+
headers:
15+
X-Content-Type-Options: [nosniff]
16+
Access-Control-Allow-Origin: ['http://localhost']
17+
Access-Control-Allow-Methods: ['HEAD', 'GET', 'OPTIONS', 'DELETE']
18+
Access-Control-Allow-Headers: ['Authorization', 'Accept']
19+
Access-Control-Max-Age: [1728000]
20+
Access-Control-Allow-Credentials: [true]
21+
Access-Control-Expose-Headers: ['Docker-Content-Digest']
22+
auth:
23+
token:
24+
realm: http://localhost/auth/realms/master/protocol/docker-v2/auth
25+
service: docker-registry
26+
issuer: http://localhost/auth/realms/master
27+
rootcertbundle: /etc/docker/registry/localhost_trust_chain.pem
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICmzCCAYMCBgF3SzuJuTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0
3+
ZXIwHhcNMjEwMTI4MjMwMDI5WhcNMzEwMTI4MjMwMjA5WjARMQ8wDQYDVQQDDAZt
4+
YXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCliGzyYtDTTmaj
5+
QYso8nxIY2tO5ITnRbgDQIXMuAY5HMv2XglT2zSHJNfC/HYSilZPSd8Ee/0oxm/q
6+
On1Al3JENx21txUWOBe48CVzLlIYlUnIXqaFh0YyL6feUZaDKg1YSVGhSzHDI57X
7+
DcfnR+g0V5QxzIKzK624Lw7vqGvZz5e9sS9mTn9EZUmqQRQBerB5qrPnuDLxEbj4
8+
LPxqjuFyKQ4g8wooYlBNSFruRas3TpG/90Xy15pa9a3ofiVPZCt3IoaQGPw4Ah3O
9+
ygnelgEhWNRwROx4dErmW2l7dUQP8dbSz+qI4g04Wx3GjnZAlY7mt7LG8OncavsA
10+
Gp52m8QrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGvqPm9nmEMfGYjWN6JlnM2x
11+
/YkKzvH6B+IeXz/j2bXKZqQoQ1up62HNM4e3GKSw/51Lge6QXqgOZmFSHABev8EV
12+
+vzDMfRLjBfV1RmhZXYCh6nje0d61jAa0Sn6CfsUllIQRt3Hn67qzPk1d6SnKSHA
13+
tsbh5+pCDivfJBRm7sJCv1y9dPP1rlaxAOZrVU8LEsJlTP3D0OScrDQv09CVonwi
14+
4W2bnLcB6aPW5Fw3gyY4TtXfcQzQqbV5Gjs9EZNA6Vczu+80U14T4VD9CDgC8yky
15+
2KY2pGClWEjM+dJnZ0440wXuGK/lJEN41SzfKxyfCBTJVebwOXsqsyonYYK8Pxo=
16+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCliGzyYtDTTmaj
3+
QYso8nxIY2tO5ITnRbgDQIXMuAY5HMv2XglT2zSHJNfC/HYSilZPSd8Ee/0oxm/q
4+
On1Al3JENx21txUWOBe48CVzLlIYlUnIXqaFh0YyL6feUZaDKg1YSVGhSzHDI57X
5+
DcfnR+g0V5QxzIKzK624Lw7vqGvZz5e9sS9mTn9EZUmqQRQBerB5qrPnuDLxEbj4
6+
LPxqjuFyKQ4g8wooYlBNSFruRas3TpG/90Xy15pa9a3ofiVPZCt3IoaQGPw4Ah3O
7+
ygnelgEhWNRwROx4dErmW2l7dUQP8dbSz+qI4g04Wx3GjnZAlY7mt7LG8OncavsA
8+
Gp52m8QrAgMBAAECggEAS7VHztwvElXrT4Ost/+fpCQEckLGHlievq4GBAmunvRy
9+
vK1pqra5IY5SOFXrUrN+oijxHUXwFXQcv44ctywNEPH8Xp3rwQvKncUH+9QVrDSr
10+
WD8h/jROgKmXJ3E9W6QiEl0GPrT7f3qNLWKaKUeUBkx/9P7KUFQL0g+Dz0zCdw1k
11+
wzJNE+tJgspeInlgylmXsuT2A0lPkIDq6uxZY/yhaEG03dytX12Mmg5VcfIzjLWB
12+
cF0hKDbb+Pmu+tWbLHEwHg00Qj3k7Y21u9DrHRC0uBYrVsytSxDIuayRfwskAcIm
13+
fyMNAYVtOQxzdaDm2OTT0SAZJOOxa+ZYMGToEzzFcQKBgQDTp961U0RAlbfygA4a
14+
IDxSvLDaIQ1pZuDzHwm/8b7SNGWiTSCjSWXZFd35rOXian53u69ey61ZTIAyv1sQ
15+
c7R1yCfyg8YUTWZLbl1s5Bb+ekh64WEaMmf+eChcegsXt/kW/wQKM9DLRMAM4v5Q
16+
9g3VEMH5xuFdGRa2AwDVZrRB5wKBgQDINsNkPgxMq+B1GfnT6PpuyAWlb7GuUENc
17+
yAucIBMGKdY8QlwnQmAaKPTl1t/MFsiiwUhRJiyXurG+skd3BmMPdACWnXa7nKBW
18+
XoXM6MRhS1QrDds6hid1usO86fB50UhupSr3tkHdeWy8l1erll/rhrParSyer1iK
19+
AjLfwx2rHQKBgQCaGTehpv0jVJ43tZoO1Xd1+aF9PuFH4zpWaDut/zEiVDnHAAaK
20+
O+8mLbCOjp5UyZpITGKzTvFn+bXAvOdtRACYXGERRXWa5Htc4f6tQCepoZhRtvP+
21+
ocJrWEpygfy/iReW8ZacYvtaczSsbTwh7/NENE42L+F26cRKQkeCF6OX8wKBgQDF
22+
Y1hXp+SwYnO0f5uSlIryVTlb1TazyGXhP0hS8DxRQ0X3uuTnv8THhcGMJ8AUkhHU
23+
hAIsHxqvrFw4ycMzUZSwU4mQ9EVuygg5no8DaijSU1Xz7IFKvaCBrVP1GB8BupdS
24+
nnwyI/njxCaz9/FzNZnztqXy3fCzseP0jB5kBRVm8QKBgEDbzlXPdF5Jr4mb3+Pt
25+
hqhAP1Swx2vwIyooDqhP0D5nUV1EDJ6Hqwb6L9R0Apt2gLW0YD1ETh9TsqUOWbIm
26+
kiE9IDHZFbtBj014clx3if6snKarrjaHQJ9THzB5uwtv8b3/k81T/IyAnPmXobQx
27+
Rrw+6hjfOdQsT90raYyhRWWx
28+
-----END PRIVATE KEY-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICmzCCAYMCBgF3SzrlgjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0
3+
ZXIwHhcNMjEwMTI4MjI1OTQ3WhcNMzEwMTI4MjMwMTI3WjARMQ8wDQYDVQQDDAZt
4+
YXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHFs8UdWQrDZ/K
5+
RMvX/bu4SYa4fq38OHCu/ci8c/Vjuz8A4TI6hn84i82WLdHsiXpi4PO4YnGxTxHF
6+
vI8cAarLW2aYv8ZK81YP63kPUMGS3dMtQE8CRTAtHBOVXrxp6ab18kTeQZ/KxiXy
7+
nQM4rM7nSy+zoCY1AcPAvzLz0uqD/D8jBvsR90mrnHGrS2cJZCvaKkwfpMddUBOv
8+
qnlrDjiv0T5PE0fShvZWlUJn4tVhjwXGv6Nu32o87BnEE+6LoQek8YqaXohQLE3V
9+
MVFOHnxmCr7JwMk1Cdr+R2WyHleeC+FTL+O+kcpAzz9DIAhhUs2bHMR2OJAP3l4X
10+
PfPPGeWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIy0ru1wK6HTqO2UrNxa/Yqa
11+
GXdppS5tj2ssivKBqTc88TK2n8ORUyVVDeGVvicgASkru7ZjepR6APhdDG5Gy44x
12+
xnj/207txc6YXz+TS/2JO5SMNWrwQCwmjT9Ld4HtjEzzxt3O7tGUmSpRIAoRo4Zy
13+
gpaGuiHvJ0twhZWwcFS4sEYuGfF3uQ+hR2MQuSVm9El6GihY6c7dpv7E5GL71dDm
14+
VPjuN9/rkxEUvFl0EHZByfUpqXnVhujEDgw8eSyOleIFNJ0vEKKJRnbwcIi2SQ2z
15+
zPZjXKdr03R7YukmdMV5X7Swn7ehwF6AJijw6zpCoKcaiOQYexGMXxasK2xXw90=
16+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cc04bebe-cb07-4384-b886-3670f757fd2e
Binary file not shown.

0 commit comments

Comments
 (0)