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:
Jguer 2022-07-07 14:28:04 +00:00 committed by GitHub
parent 5a65a12278
commit b79b53cbdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 5698 additions and 10 deletions

View File

@ -576,7 +576,7 @@ tls_client_cert =
tls_client_key =
tls_client_ca =
use_pkce = false
auth_style =
auth_style =
#################################### Basic Auth ##########################
[auth.basic]
@ -597,6 +597,7 @@ enable_login_token = false
#################################### Auth JWT ##########################
[auth.jwt]
enabled = false
enable_login_token = false
header_name =
email_claim =
username_claim =
@ -762,7 +763,7 @@ instrumentations_console_enabled = false
instrumentations_webvitals_enabled = false
# Api Key, only applies to Grafana Javascript Agent provider
api_key =
api_key =
#################################### Usage Quotas ########################
[quota]

File diff suppressed because it is too large Load Diff

View File

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

View 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

View 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"}]}

View 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

View 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"}]}

View File

@ -1,6 +1,6 @@
# OAUTH BLOCK
## Devenv setup
## Devenv setup oauth
To launch the block, use the oauth source. Ex:
```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'
```
## 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
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
```
1. Start from beginning of this readme

View File

@ -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}
err := hs.loginUserWithUser(user, c)
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
}
}

View File

@ -2,6 +2,7 @@ package contexthandler
import (
"errors"
"net/http"
"github.com/grafana/grafana/pkg/login"
"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)
if err != nil {
ctx.Logger.Debug("Failed to verify JWT", "error", err)
ctx.JsonApiErr(401, InvalidJWT, err)
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
return true
}
@ -33,7 +34,7 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
if sub == "" {
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
}
extUser := &models.ExternalUserInfo{
@ -56,7 +57,7 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
if query.Login == "" && query.Email == "" {
ctx.Logger.Debug("Failed to get an authentication claim from JWT")
ctx.JsonApiErr(401, InvalidJWT, err)
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
return true
}
@ -80,10 +81,10 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
"username_claim", query.Login,
)
err = login.ErrInvalidCredentials
ctx.JsonApiErr(401, UserNotFound, err)
ctx.JsonApiErr(http.StatusUnauthorized, UserNotFound, err)
} else {
ctx.Logger.Error("Failed to get signed in user", "error", err)
ctx.JsonApiErr(401, InvalidJWT, err)
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
}
return true
}