mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
JWT: Add JWT proxy setup devenv (#51731)
* JWT: Add JWT Auth devenv * Auth: JWT allow retrieving login token Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * JWT: Add JWT Auth Proxy devenv * JWT: Add instructions to readme * JWT: Add JWT users * JWT: Remove oauth users * revert session changes, unnecessary Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
parent
5a65a12278
commit
b79b53cbdb
@ -597,6 +597,7 @@ enable_login_token = false
|
|||||||
#################################### Auth JWT ##########################
|
#################################### Auth JWT ##########################
|
||||||
[auth.jwt]
|
[auth.jwt]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
enable_login_token = false
|
||||||
header_name =
|
header_name =
|
||||||
email_claim =
|
email_claim =
|
||||||
username_claim =
|
username_claim =
|
||||||
|
5486
devenv/docker/blocks/jwt_proxy/cloak.sql
Normal file
5486
devenv/docker/blocks/jwt_proxy/cloak.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,9 @@
|
|||||||
|
#/bin/sh
|
||||||
|
|
||||||
|
VERSION=12.0.1 # set version here
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
git clone git@github.com:keycloak/keycloak-containers.git
|
||||||
|
cd keycloak-containers/server
|
||||||
|
git checkout $VERSION
|
||||||
|
docker build -t "quay.io/keycloak/keycloak:${VERSION}" .
|
54
devenv/docker/blocks/jwt_proxy/docker-compose.yaml
Normal file
54
devenv/docker/blocks/jwt_proxy/docker-compose.yaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
oauthkeycloakdb:
|
||||||
|
image: postgres:12.2
|
||||||
|
container_name: oauthkeycloakdb
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: keycloak
|
||||||
|
POSTGRES_USER: keycloak
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
volumes:
|
||||||
|
- ./docker/blocks/jwt_proxy/cloak.sql:/docker-entrypoint-initdb.d/cloak.sql
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
oauthkeycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:12.0.1
|
||||||
|
container_name: oauthkeycloak
|
||||||
|
environment:
|
||||||
|
DB_VENDOR: POSTGRES
|
||||||
|
DB_ADDR: oauthkeycloakdb
|
||||||
|
DB_DATABASE: keycloak
|
||||||
|
DB_USER: keycloak
|
||||||
|
DB_PASSWORD: password
|
||||||
|
KEYCLOAK_USER: admin
|
||||||
|
KEYCLOAK_PASSWORD: admin
|
||||||
|
PROXY_ADDRESS_FORWARDING: "true"
|
||||||
|
ports:
|
||||||
|
- 8087:8080
|
||||||
|
depends_on:
|
||||||
|
- oauthkeycloakdb
|
||||||
|
links:
|
||||||
|
- "oauthkeycloakdb:oauthkeycloakdb"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
oauthproxy:
|
||||||
|
image: docker.io/bitnami/oauth2-proxy:7.3.0
|
||||||
|
container_name: oauthproxy
|
||||||
|
command: [
|
||||||
|
"--cookie-secret=yI-CWT5s4sBR2Zd0DDJJlTYc0aQ3jwGH15jYA18ZAQA=",
|
||||||
|
"--upstream=http://localhost:3000",
|
||||||
|
"--provider=keycloak",
|
||||||
|
"--client-id=grafana-oauth",
|
||||||
|
"--client-secret=d17b9ea9-bcb1-43d2-b132-d339e55872a8",
|
||||||
|
"--login-url=http://127.0.0.1:8087/auth/realms/grafana/protocol/openid-connect/auth",
|
||||||
|
"--redeem-url=http://127.0.0.1:8087/auth/realms/grafana/protocol/openid-connect/token",
|
||||||
|
"--profile-url=http://127.0.0.1:8087/auth/realms/grafana/protocol/openid-connect/userinfo",
|
||||||
|
"--validate-url=http://127.0.0.1:8087/auth/realms/grafana/protocol/openid-connect/userinfo",
|
||||||
|
"--cookie-secure=false",
|
||||||
|
"--http-address=0.0.0.0:8088",
|
||||||
|
"--redirect-url=http://127.0.0.1:8088/oauth2/callback",
|
||||||
|
"--pass-access-token=true",
|
||||||
|
"--email-domain=*",
|
||||||
|
]
|
||||||
|
network_mode: "host"
|
||||||
|
depends_on:
|
||||||
|
- oauthkeycloak
|
||||||
|
restart: unless-stopped
|
1
devenv/docker/blocks/jwt_proxy/jwks.json
Normal file
1
devenv/docker/blocks/jwt_proxy/jwks.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"keys":[{"kid":"On2FQuJ8Y-909uJGWQEDkbzG-GRNmMc43HslEgVv_VQ","kty":"RSA","alg":"RS256","use":"sig","n":"qDmQHfTcOQOzmNJbVvtvuS8p_EgmiscP7vA_PZNyKx9O7utyGuoAmJH8e2w8gLIDDWHl5_x8aAIl_-TTPTSiyX8I68ryIdR28ZSe5u4pRdpXCVvJpOefKNIxQCTH7rs4KuRj0HZ2u1mu1Vz5_CeCCoKwKSmheD3u1xTJ8-VxQmdqfGxhuKtnkof7977HWOWy4GLDFqxyYHgihP_MmSeTmXUhVeZI6IOCqHMpF8eFWVGKM6V8rIKf8QO2K_vDJBM_3C933vMY8mqSQXbI3G54x-0myAaQXr4JkxjvUGKg5YC3ZXw7AjfZv_W_fQOG0GYp2hQ0akR4KNKT3XPNmpMVlQ","e":"AQAB","x5c":["MIICnTCCAYUCBgF+u1ir8jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdncmFmYW5hMB4XDTIyMDIwMjE2NDkxN1oXDTMyMDIwMjE2NTA1N1owEjEQMA4GA1UEAwwHZ3JhZmFuYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKg5kB303DkDs5jSW1b7b7kvKfxIJorHD+7wPz2TcisfTu7rchrqAJiR/HtsPICyAw1h5ef8fGgCJf/k0z00osl/COvK8iHUdvGUnubuKUXaVwlbyaTnnyjSMUAkx+67OCrkY9B2drtZrtVc+fwnggqCsCkpoXg97tcUyfPlcUJnanxsYbirZ5KH+/e+x1jlsuBiwxascmB4IoT/zJknk5l1IVXmSOiDgqhzKRfHhVlRijOlfKyCn/EDtiv7wyQTP9wvd97zGPJqkkF2yNxueMftJsgGkF6+CZMY71BioOWAt2V8OwI32b/1v30DhtBmKdoUNGpEeCjSk91zzZqTFZUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEABlW64QxuREB81VMGsyhj4Q5RykFaVuD5O8YlwUpmVfAVLzb0Drf54Kn4bnpnckKyYV+T+HsN4QXt81UE41xH0Aai2H3vrGH+PJf6aLPCDE+jpMqtN3n6IgImJXJPL8upMfhhWDv4nkM4uynEwWupzmrKi4oJuTETSMktJby4o6//XWnCzCVMoAGFJU4gtjBUzOMLW26zD+yc+BuUtfR3HzItVHSZKQSNSFO0kVS68RgrER8qJw07z3BOJ2bPpPM0PYyEngGMaowz/T6lI32ymGMWYMAnslthS1KAW9xcTBwnrW1nMhe5a0LPxIktys/wJtxIHZLc5sOddGT4xYklLg=="],"x5t":"prs-h1NBqOSJMH-tQWLTqguWets","x5t#S256":"YjK3HobZW8xbNL1IPDgFhCM41UC5c0hG2cxaF6v961Q"}]}
|
66
devenv/docker/blocks/jwt_proxy/readme.md
Normal file
66
devenv/docker/blocks/jwt_proxy/readme.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# OAUTH BLOCK
|
||||||
|
## Devenv setup jwt auth
|
||||||
|
|
||||||
|
To launch the block, use the oauth source. Ex:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make devenv sources="jwt_proxy"
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is the conf you need to add to your configuration file (conf/custom.ini):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[auth]
|
||||||
|
signout_redirect_url = http://127.0.0.1:8088/oauth2/sign_out
|
||||||
|
|
||||||
|
[auth.jwt]
|
||||||
|
enabled = true
|
||||||
|
enable_login_token = true
|
||||||
|
header_name = X-Forwarded-Access-Token
|
||||||
|
username_claim = login
|
||||||
|
email_claim = email
|
||||||
|
jwk_set_file = devenv/docker/blocks/oauth/jwks.json
|
||||||
|
cache_ttl = 60m
|
||||||
|
expected_claims = {"iss": "http://localhost:8087/auth/realms/grafana", "azp": "grafana-oauth"}
|
||||||
|
auto_sign_up = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Access Grafana through:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
http://127.0.0.1:8088
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backing up keycloak DB
|
||||||
|
|
||||||
|
In case you want to make changes to the devenv setup, you can dump keycloack's DB:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd devenv;
|
||||||
|
docker-compose exec -T oauthkeycloakdb bash -c "pg_dump -U keycloak keycloak" > docker/blocks/oauth/cloak.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connecting to keycloack:
|
||||||
|
|
||||||
|
- keycloak admin: http://localhost:8087
|
||||||
|
- keycloak admin login: admin:admin
|
||||||
|
- grafana jwt viewer login: jwt-viewer:grafana
|
||||||
|
- grafana jwt editor login: jwt-editor:grafana
|
||||||
|
- grafana jwt admin login: jwt-admin:grafana
|
||||||
|
|
||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
## Mac M1 Users
|
||||||
|
|
||||||
|
The new arm64 architecture does not build for the latest docker image of keycloack. Refer to https://github.com/docker/for-mac/issues/5310 for the issue to see if it resolved.
|
||||||
|
Until then you need to build the docker image locally and then run `devenv`.
|
||||||
|
|
||||||
|
1. Remove any lingering keycloack image
|
||||||
|
```sh
|
||||||
|
$ docker rmi $(docker images | grep 'keycloack')
|
||||||
|
```
|
||||||
|
1. Build keycloack image locally
|
||||||
|
```sh
|
||||||
|
$ ./docker-build-keycloack-m1-image.sh
|
||||||
|
```
|
||||||
|
1. Start from beginning of this readme
|
1
devenv/docker/blocks/oauth/jwks.json
Normal file
1
devenv/docker/blocks/oauth/jwks.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"keys":[{"kid":"On2FQuJ8Y-909uJGWQEDkbzG-GRNmMc43HslEgVv_VQ","kty":"RSA","alg":"RS256","use":"sig","n":"qDmQHfTcOQOzmNJbVvtvuS8p_EgmiscP7vA_PZNyKx9O7utyGuoAmJH8e2w8gLIDDWHl5_x8aAIl_-TTPTSiyX8I68ryIdR28ZSe5u4pRdpXCVvJpOefKNIxQCTH7rs4KuRj0HZ2u1mu1Vz5_CeCCoKwKSmheD3u1xTJ8-VxQmdqfGxhuKtnkof7977HWOWy4GLDFqxyYHgihP_MmSeTmXUhVeZI6IOCqHMpF8eFWVGKM6V8rIKf8QO2K_vDJBM_3C933vMY8mqSQXbI3G54x-0myAaQXr4JkxjvUGKg5YC3ZXw7AjfZv_W_fQOG0GYp2hQ0akR4KNKT3XPNmpMVlQ","e":"AQAB","x5c":["MIICnTCCAYUCBgF+u1ir8jANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdncmFmYW5hMB4XDTIyMDIwMjE2NDkxN1oXDTMyMDIwMjE2NTA1N1owEjEQMA4GA1UEAwwHZ3JhZmFuYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKg5kB303DkDs5jSW1b7b7kvKfxIJorHD+7wPz2TcisfTu7rchrqAJiR/HtsPICyAw1h5ef8fGgCJf/k0z00osl/COvK8iHUdvGUnubuKUXaVwlbyaTnnyjSMUAkx+67OCrkY9B2drtZrtVc+fwnggqCsCkpoXg97tcUyfPlcUJnanxsYbirZ5KH+/e+x1jlsuBiwxascmB4IoT/zJknk5l1IVXmSOiDgqhzKRfHhVlRijOlfKyCn/EDtiv7wyQTP9wvd97zGPJqkkF2yNxueMftJsgGkF6+CZMY71BioOWAt2V8OwI32b/1v30DhtBmKdoUNGpEeCjSk91zzZqTFZUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEABlW64QxuREB81VMGsyhj4Q5RykFaVuD5O8YlwUpmVfAVLzb0Drf54Kn4bnpnckKyYV+T+HsN4QXt81UE41xH0Aai2H3vrGH+PJf6aLPCDE+jpMqtN3n6IgImJXJPL8upMfhhWDv4nkM4uynEwWupzmrKi4oJuTETSMktJby4o6//XWnCzCVMoAGFJU4gtjBUzOMLW26zD+yc+BuUtfR3HzItVHSZKQSNSFO0kVS68RgrER8qJw07z3BOJ2bPpPM0PYyEngGMaowz/T6lI32ymGMWYMAnslthS1KAW9xcTBwnrW1nMhe5a0LPxIktys/wJtxIHZLc5sOddGT4xYklLg=="],"x5t":"prs-h1NBqOSJMH-tQWLTqguWets","x5t#S256":"YjK3HobZW8xbNL1IPDgFhCM41UC5c0hG2cxaF6v961Q"}]}
|
@ -1,6 +1,6 @@
|
|||||||
# OAUTH BLOCK
|
# OAUTH BLOCK
|
||||||
|
|
||||||
## Devenv setup
|
## Devenv setup oauth
|
||||||
|
|
||||||
To launch the block, use the oauth source. Ex:
|
To launch the block, use the oauth source. Ex:
|
||||||
```bash
|
```bash
|
||||||
@ -29,6 +29,76 @@ api_url = http://localhost:8087/auth/realms/grafana/protocol/openid-connect/user
|
|||||||
role_attribute_path = contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'
|
role_attribute_path = contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Devenv setup jwt auth
|
||||||
|
|
||||||
|
To launch the block, use the oauth source. Ex:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make devenv sources="oauth"
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is the conf you need to add to your configuration file (conf/custom.ini):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[auth.jwt]
|
||||||
|
enabled = true
|
||||||
|
header_name = X-JWT-Assertion
|
||||||
|
username_claim = login
|
||||||
|
email_claim = email
|
||||||
|
jwk_set_file = devenv/docker/blocks/oauth/jwks.json
|
||||||
|
cache_ttl = 60m
|
||||||
|
expected_claims = {"iss": "http://localhost:8087/auth/realms/grafana", "azp": "grafana-oauth"}
|
||||||
|
auto_sign_up = true
|
||||||
|
```
|
||||||
|
|
||||||
|
You can obtain a jwt token by using the following command for oauth-admin:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl --request POST \
|
||||||
|
--url http://localhost:8087/auth/realms/grafana/protocol/openid-connect/token \
|
||||||
|
--header 'Content-Type: application/x-www-form-urlencoded' \
|
||||||
|
--data client_id=grafana-oauth \
|
||||||
|
--data grant_type=password \
|
||||||
|
--data client_secret=d17b9ea9-bcb1-43d2-b132-d339e55872a8 \
|
||||||
|
--data scope=openid \
|
||||||
|
--data username=oauth-admin \
|
||||||
|
--data password=grafana
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Grafana call example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl --request GET \
|
||||||
|
--url http://127.0.0.1:3000/api/folders \
|
||||||
|
--header 'Accept: application/json' \
|
||||||
|
--header 'X-JWT-Assertion: eyJ......'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alternative devenv setup jwk_set_url
|
||||||
|
|
||||||
|
Run a reverse proxy pointing to the jwk_set_url (only an https-uri can be used as jwk_set_url).
|
||||||
|
|
||||||
|
Ex (using localtunnel):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx localtunnel --port 8087
|
||||||
|
```
|
||||||
|
|
||||||
|
And using the following conf:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[auth.jwt]
|
||||||
|
enabled = true
|
||||||
|
header_name = X-JWT-Assertion
|
||||||
|
username_claim = login
|
||||||
|
email_claim = email
|
||||||
|
jwk_set_url = <YOUR REVERSE PROXY URL>/auth/realms/grafana/protocol/openid-connect/certs
|
||||||
|
cache_ttl = 60m
|
||||||
|
expected_claims = {"iss": "http://localhost:8087/auth/realms/grafana", "azp": "grafana-oauth"}
|
||||||
|
auto_sign_up = true
|
||||||
|
```
|
||||||
|
|
||||||
## Backing up keycloak DB
|
## Backing up keycloak DB
|
||||||
|
|
||||||
In case you want to make changes to the devenv setup, you can dump keycloack's DB:
|
In case you want to make changes to the devenv setup, you can dump keycloack's DB:
|
||||||
@ -62,4 +132,3 @@ $ docker rmi $(docker images | grep 'keycloack')
|
|||||||
$ ./docker-build-keycloack-m1-image.sh
|
$ ./docker-build-keycloack-m1-image.sh
|
||||||
```
|
```
|
||||||
1. Start from beginning of this readme
|
1. Start from beginning of this readme
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
|||||||
user := &user.User{ID: c.SignedInUser.UserId, Email: c.SignedInUser.Email, Login: c.SignedInUser.Login}
|
user := &user.User{ID: c.SignedInUser.UserId, Email: c.SignedInUser.Email, Login: c.SignedInUser.Login}
|
||||||
err := hs.loginUserWithUser(user, c)
|
err := hs.loginUserWithUser(user, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Handle(hs.Cfg, 500, "Failed to sign in user", err)
|
c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to sign in user", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package contexthandler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/login"
|
"github.com/grafana/grafana/pkg/login"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@ -23,7 +24,7 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
|
|||||||
claims, err := h.JWTAuthService.Verify(ctx.Req.Context(), jwtToken)
|
claims, err := h.JWTAuthService.Verify(ctx.Req.Context(), jwtToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Logger.Debug("Failed to verify JWT", "error", err)
|
ctx.Logger.Debug("Failed to verify JWT", "error", err)
|
||||||
ctx.JsonApiErr(401, InvalidJWT, err)
|
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
|
|||||||
|
|
||||||
if sub == "" {
|
if sub == "" {
|
||||||
ctx.Logger.Warn("Got a JWT without the mandatory 'sub' claim", "error", err)
|
ctx.Logger.Warn("Got a JWT without the mandatory 'sub' claim", "error", err)
|
||||||
ctx.JsonApiErr(401, InvalidJWT, err)
|
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
extUser := &models.ExternalUserInfo{
|
extUser := &models.ExternalUserInfo{
|
||||||
@ -56,7 +57,7 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
|
|||||||
|
|
||||||
if query.Login == "" && query.Email == "" {
|
if query.Login == "" && query.Email == "" {
|
||||||
ctx.Logger.Debug("Failed to get an authentication claim from JWT")
|
ctx.Logger.Debug("Failed to get an authentication claim from JWT")
|
||||||
ctx.JsonApiErr(401, InvalidJWT, err)
|
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,10 +81,10 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
|
|||||||
"username_claim", query.Login,
|
"username_claim", query.Login,
|
||||||
)
|
)
|
||||||
err = login.ErrInvalidCredentials
|
err = login.ErrInvalidCredentials
|
||||||
ctx.JsonApiErr(401, UserNotFound, err)
|
ctx.JsonApiErr(http.StatusUnauthorized, UserNotFound, err)
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Error("Failed to get signed in user", "error", err)
|
ctx.Logger.Error("Failed to get signed in user", "error", err)
|
||||||
ctx.JsonApiErr(401, InvalidJWT, err)
|
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user