Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DONT MERGE] Allow Kibana authentication via JWT for the predefined set of routes. #159117

Closed
wants to merge 1 commit into from

Conversation

azasypkin
Copy link
Member

@azasypkin azasypkin commented Jun 6, 2023

Summary

In this PoC, I introduce the conditional xpack.security.authc.http.jwt.restrictToPaths configuration (available only in the serverless context) that restricts usage of the JWT credentials only to a set of pre-configured Kibana paths:

xpack.security.authc:
  http:
    jwt.restrictToPaths: ["/internal/security/me", "/api/status"]

Alternatively can move this logic from authentication layer to the authorization layer with something like this:

xpack.security.authz:
  jwt.enabled: true
router.get({
  path: '/api/status',
  options: {
    // If the client is using JWT credentials, the JWT `scope` claim must
    // include `metrics` in order to access the route. Routes without the
    // `jwt:` tag aren't available if JWT credentials are used. 
    tags: ['api', 'jwt:scope:metrics'],
  },
  ...
});

Testing

Generate keys

NOTE: Test keys mentioned here aren't secret and already publicly exposed.

openssl genrsa 2048 > jwks_private.pem
openssl rsa -in jwks_private.pem -pubout > jwks_public.pem

## https://github.com/dannycoates/pem-jwk
pem-jwk jwks_public.pem > jwks.json

JWKS (pkc_jwkset_path)

{
  "keys": [
    {
      "kty": "RSA",
      "e": "AQAB",
      "use": "sig",
      "n": "v9-88aGdE4E85PuEycxTA6LkM3TBvNScoeP6A-dd0Myo6-LfBlp1r7BPBWmvi_SC6Zam3U1LE3AekDMwqJg304my0pvh8wOwlmRpgKXDXjvj4s59vdeVNhCB9doIthUABd310o9lyb55fWc_qQYE2LK9AyEjicJswafguH6txV4IwSl13ieZAxni0Ca4CwdzXO1Oi34XjHF8F5x_0puTaQzHn5bPG4fiIJN-pwie0Ba4VEDPO5ca4lLXWVi1bn8xMDTAULrBAXJwDaDdS05KMbc4sPlyQPhtY1gcYvUbozUPYxSWwA7fZgFzV_h-uy_oXf1EXttOxSgog1z3cJzf6Q"
    }
  ]
}

Public key

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9+88aGdE4E85PuEycxT
A6LkM3TBvNScoeP6A+dd0Myo6+LfBlp1r7BPBWmvi/SC6Zam3U1LE3AekDMwqJg3
04my0pvh8wOwlmRpgKXDXjvj4s59vdeVNhCB9doIthUABd310o9lyb55fWc/qQYE
2LK9AyEjicJswafguH6txV4IwSl13ieZAxni0Ca4CwdzXO1Oi34XjHF8F5x/0puT
aQzHn5bPG4fiIJN+pwie0Ba4VEDPO5ca4lLXWVi1bn8xMDTAULrBAXJwDaDdS05K
Mbc4sPlyQPhtY1gcYvUbozUPYxSWwA7fZgFzV/h+uy/oXf1EXttOxSgog1z3cJzf
6QIDAQAB
-----END PUBLIC KEY-----

Private key

-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/37zxoZ0TgTzk
+4TJzFMDouQzdMG81Jyh4/oD513QzKjr4t8GWnWvsE8Faa+L9ILplqbdTUsTcB6Q
MzComDfTibLSm+HzA7CWZGmApcNeO+Pizn2915U2EIH12gi2FQAF3fXSj2XJvnl9
Zz+pBgTYsr0DISOJwmzBp+C4fq3FXgjBKXXeJ5kDGeLQJrgLB3Nc7U6LfheMcXwX
nH/Sm5NpDMefls8bh+Igk36nCJ7QFrhUQM87lxriUtdZWLVufzEwNMBQusEBcnAN
oN1LTkoxtziw+XJA+G1jWBxi9RujNQ9jFJbADt9mAXNX+H67L+hd/URe207FKCiD
XPdwnN/pAgMBAAECggEADiKRbMuXIsS2k7fjxGoFA5OQdCn5y8tt7o847+ivhJ5P
I3GHNJSdbt/yMlfi0tCkhEjQ6iSzjy8HUWA0CXeNRUwznEhXkOuIqsui6hNMHTkU
RLUplj63g1AcAtyZH7DUW5pKbcSanw4lLRPaIL2MxdoFCqH6WD+2e12+tFjAvHVc
Bm03+hIt2898ruLQfHLQ1MUegTmXWZ9fqiPizuKwrW9xlCGQbIKoqVHebCMqRFK0
XGv0NNmUTnNo+uF3++yYHv/TL96EFTmU7QhUFrddONzUXDv0JjyiOnHk32191R9m
V8Y9mq+RT+tMsu+dxr2Yk4Qc5oHYX84p/afQxX8sMQKBgQD9FUsnJASMeg4jNlq8
XjDsXWu0NoVGTfU9z/9/SU+L6KexubdCoCWxs+8KyA69PZkMW6HMw6BGuDPjTvI9
1DmRdnVEa5EmUv/CIXZcAM+9Q7yWEocB/JeQOj9mdC/u1/sdQxNg1ae2HqJczl2I
EO4r7YshHQmqCju4lfyEf4rWTwKBgQDCFdm535BIPa4wGRTD7tY/VOCpDeom+PxH
9LUhefJV+G9RhP2jEPW+9D/ux8YAkL4c2kZR9kddLVFAD8jwormjWk6+uL5/jAAI
j6Aor3spBTNpgji6YnRaIk2PDIznFnSDPhdoWGsw8QQQ54wUO8m51cqPSYVZIu2d
U0yYacGQRwKBgFuJkB0gEeUdYG+sATWQe/GB+Kq97YZ4O/OXf7nyMitQgxbtLTOT
6Q5VHmiv42TfGrQ1kFgXiakKhvn4W/WxBQFv7wpIPb+21XrJz52HTZwPG+7L1LkL
O2aXKsdLzup8g/8Ze7DSlk5w1hjrKzlDpmGNEX1wm0Y9XUxuM19ZIkZRAoGAFR/F
s8pWbNZxyABi1zR+kyQM07mU+6rr4nUK5drc+mhwzUGZTY9CAAebkcSik1stpfxH
3RHeEJEnH77YEwDTDal9mpqG+WDmfAgN2X/H+t37C4fF3ttqaIkFQgWOrHQwODyg
1ZWSDSCeXayl/WnIefZ/9np9DgeULyRq2Mfh7m8CgYEAsEhsZyAe7QrCoVMykUSp
sys7qht/9B4QgaeX1pPdaJxLTPIKG0gYldWF1/zyMtiCYVY+MJkwgaVRgjX+8Swa
QfluMQ4YYrurxcdn+nSggGGinM6rt0C319sduKouKChMpzoEQp2RAIH35Of2usmR
k8z9rWWE/VEBo6K1d+3g/rA=
-----END PRIVATE KEY-----

Generate JWT

{
  "alg": "RS256",
  "typ": "JWT"
}
  • Use the following payload
{
  "iss": "https://kibana.elastic.co/jwt/",
  "sub": "aleh.zasypkin@elastic.co",
  "aud": "elasticsearch",
  "name": "Aleh Zasypkin",
  "iat": 946684800,
  "exp": 4070908800
}
  • For signature use the private and public keys defined in the previous section
  • Use generated token for authentication, e.g. eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC8iLCJzdWIiOiJhbGVoLnphc3lwa2luQGVsYXN0aWMuY28iLCJhdWQiOiJlbGFzdGljc2VhcmNoIiwibmFtZSI6IkFsZWggWmFzeXBraW4iLCJpYXQiOjk0NjY4NDgwMCwiZXhwIjo0MDcwOTA4ODAwfQ.LBwLDK4CCYHjtmWZ_J0IwKP6BQjH-8LbKUu1Obj2bUAtZcGVrnO_pY1JXCG582BLegq8_RrlxZ0C8GKN-kvuFt7okPEkMqfT6yCi_gt271Xzlbe01IT6DX5WRm7nT6mjNI4USndemquxl0NxHCm07azKD4MUsYIlgp_YW14ZKmHn4fJW0qgDgt4CeRkLQm5QE--rZ7VnlOFvaAsIlC7bLHHvhj_ntMSraFJEXc1JE7va8QX_D6cXpHbszGjnm9G928gJ24XVjUqXuR23yDNcc6socTPbq8WO9tj67cknCZG1An1wtefDOOKiqMKhrHPvBz9eT1CnOm57l63K8LvulQ

Run ES

$ yarn es snapshot --license trial \
    -E xpack.security.authc.token.enabled=true \
    -E xpack.security.authc.realms.native.native1.order=0 \
    -E xpack.security.authc.realms.jwt.jwt1.order=1 \
    -E xpack.security.authc.realms.jwt.jwt1.token_type=access_token \
    -E xpack.security.authc.realms.jwt.jwt1.client_authentication.type=shared_secret \
    -E xpack.security.authc.realms.jwt.jwt1.client_authentication.shared_secret=my_super_secret \
    -E xpack.security.authc.realms.jwt.jwt1.allowed_issuer=https://kibana.elastic.co/jwt/ \
    -E xpack.security.authc.realms.jwt.jwt1.allowed_subjects=aleh.zasypkin@elastic.co \
    -E xpack.security.authc.realms.jwt.jwt1.allowed_audiences=elasticsearch \
    -E xpack.security.authc.realms.jwt.jwt1.allowed_signature_algorithms=[RS256] \
    -E xpack.security.authc.realms.jwt.jwt1.claims.principal=sub \
    -E xpack.security.authc.realms.jwt.jwt1.pkc_jwkset_path=/.../jwks.json

Run Kibana

yarn start --serverless \
    --elasticsearch.requestHeadersWhitelist='["authorization", "ES-Client-Authentication"]' \
    --xpack.security.authc.http.jwt.restrictToPaths='["/internal/security/me"]'

Authenticate with JWT

Elasticsearch endpoint

GET http://localhost:9200/_security/_authenticate
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC8iLCJzdWIiOiJhbGVoLnphc3lwa2luQGVsYXN0aWMuY28iLCJhdWQiOiJlbGFzdGljc2VhcmNoIiwibmFtZSI6IkFsZWggWmFzeXBraW4iLCJpYXQiOjk0NjY4NDgwMCwiZXhwIjo0MDcwOTA4ODAwfQ.LBwLDK4CCYHjtmWZ_J0IwKP6BQjH-8LbKUu1Obj2bUAtZcGVrnO_pY1JXCG582BLegq8_RrlxZ0C8GKN-kvuFt7okPEkMqfT6yCi_gt271Xzlbe01IT6DX5WRm7nT6mjNI4USndemquxl0NxHCm07azKD4MUsYIlgp_YW14ZKmHn4fJW0qgDgt4CeRkLQm5QE--rZ7VnlOFvaAsIlC7bLHHvhj_ntMSraFJEXc1JE7va8QX_D6cXpHbszGjnm9G928gJ24XVjUqXuR23yDNcc6socTPbq8WO9tj67cknCZG1An1wtefDOOKiqMKhrHPvBz9eT1CnOm57l63K8LvulQ
ES-Client-Authentication: SharedSecret my_super_secret
Accept: application/json
{
  "username": "aleh.zasypkin@elastic.co",
  "roles": [],
  "full_name": null,
  "email": null,
  "metadata": {
    "jwt_claim_aud": [
      "elasticsearch"
    ],
    "jwt_claim_name": "Aleh Zasypkin",
    "jwt_claim_iss": "https://kibana.elastic.co/jwt/",
    "jwt_token_type": "access_token",
    "jwt_claim_sub": "aleh.zasypkin@elastic.co"
  },
  "enabled": true,
  "authentication_realm": {
    "name": "jwt1",
    "type": "jwt"
  },
  "lookup_realm": {
    "name": "jwt1",
    "type": "jwt"
  },
  "authentication_type": "realm"
}

Kibana endpoint

GET http://localhost:5601/internal/security/me
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC8iLCJzdWIiOiJhbGVoLnphc3lwa2luQGVsYXN0aWMuY28iLCJhdWQiOiJlbGFzdGljc2VhcmNoIiwibmFtZSI6IkFsZWggWmFzeXBraW4iLCJpYXQiOjk0NjY4NDgwMCwiZXhwIjo0MDcwOTA4ODAwfQ.LBwLDK4CCYHjtmWZ_J0IwKP6BQjH-8LbKUu1Obj2bUAtZcGVrnO_pY1JXCG582BLegq8_RrlxZ0C8GKN-kvuFt7okPEkMqfT6yCi_gt271Xzlbe01IT6DX5WRm7nT6mjNI4USndemquxl0NxHCm07azKD4MUsYIlgp_YW14ZKmHn4fJW0qgDgt4CeRkLQm5QE--rZ7VnlOFvaAsIlC7bLHHvhj_ntMSraFJEXc1JE7va8QX_D6cXpHbszGjnm9G928gJ24XVjUqXuR23yDNcc6socTPbq8WO9tj67cknCZG1An1wtefDOOKiqMKhrHPvBz9eT1CnOm57l63K8LvulQ
ES-Client-Authentication: SharedSecret my_super_secret
Accept: application/json
{
  "username": "aleh.zasypkin@elastic.co",
  "roles": [],
  "full_name": null,
  "email": null,
  "metadata": {
    "jwt_token_type": "access_token",
    "jwt_claim_iss": "https://kibana.elastic.co/jwt/",
    "jwt_claim_name": "Aleh Zasypkin",
    "jwt_claim_aud": [
      "elasticsearch"
    ],
    "jwt_claim_sub": "aleh.zasypkin@elastic.co"
  },
  "enabled": true,
  "authentication_realm": {
    "name": "jwt1",
    "type": "jwt"
  },
  "lookup_realm": {
    "name": "jwt1",
    "type": "jwt"
  },
  "authentication_type": "realm",
  "authentication_provider": {
    "type": "http",
    "name": "__http__"
  },
  "elastic_cloud_user": false
}

Try not allowed endpoint:

GET http://localhost:5601/api/status
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC8iLCJzdWIiOiJhbGVoLnphc3lwa2luQGVsYXN0aWMuY28iLCJhdWQiOiJlbGFzdGljc2VhcmNoIiwibmFtZSI6IkFsZWggWmFzeXBraW4iLCJpYXQiOjk0NjY4NDgwMCwiZXhwIjo0MDcwOTA4ODAwfQ.LBwLDK4CCYHjtmWZ_J0IwKP6BQjH-8LbKUu1Obj2bUAtZcGVrnO_pY1JXCG582BLegq8_RrlxZ0C8GKN-kvuFt7okPEkMqfT6yCi_gt271Xzlbe01IT6DX5WRm7nT6mjNI4USndemquxl0NxHCm07azKD4MUsYIlgp_YW14ZKmHn4fJW0qgDgt4CeRkLQm5QE--rZ7VnlOFvaAsIlC7bLHHvhj_ntMSraFJEXc1JE7va8QX_D6cXpHbszGjnm9G928gJ24XVjUqXuR23yDNcc6socTPbq8WO9tj67cknCZG1An1wtefDOOKiqMKhrHPvBz9eT1CnOm57l63K8LvulQ
ES-Client-Authentication: SharedSecret my_super_secret
Accept: application/json
[2023-06-07T12:00:46.233+02:00][ERROR][plugins.security.http] Attempted to authenticate with JWT credentials against /api/status, but it's not allowed.
{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Unauthorized"
}

Related: #153720

@azasypkin azasypkin added skip-ci ci:no-auto-commit Disable auto-committing changes on CI labels Jun 6, 2023
@azasypkin azasypkin self-assigned this Jun 6, 2023
@azasypkin
Copy link
Member Author

Closing in favor of the issue for the proper implementation #162632

@azasypkin azasypkin closed this Jul 27, 2023
@azasypkin azasypkin deleted the issue-xxx-jwt-poc branch August 14, 2023 11:28
azasypkin added a commit that referenced this pull request Aug 23, 2023
…tes only. (#163806)

## Summary

Allow Kibana to restrict the usage of JWT for a predefined set of routes
only in Serverless environment by default. This capability is not
available in non-Serverless environment.

Any route that needs to be accessed in Serverless environemnt using JWT
as a means of authentication should include `security:acceptJWT` tag.

## How to test

If you'd like to generate your own JWT to test the PR, please follow the
steps outlined in
#159117 (comment) or just
run functional test server and use static JWT from the Serverless test.

This PR also generated a Serverless Docker image that you can use in
your Dev/QA MKI cluster.

- [x] Implementation functionality and add unit tests
- [x] Update metrics/status routes to include new `security:acceptJWT`
tag
- [x] Update serverless test suite to include a test for
`security:acceptJWT`

__Fixes: https://github.com/elastic/kibana/issues/162632__

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
azasypkin added a commit that referenced this pull request Dec 4, 2023
## Summary

In this PR we:
* Allow using JWT credentials to grant API keys
* Extend default value of `elasticsearch.requestHeadersWhitelist` to
include both `authorization` and `es-client-authentication` to support
JWT with required client authentication _by default_. See
https://www.elastic.co/guide/en/elasticsearch/reference/8.11/jwt-auth-realm.html#jwt-realm-configuration
* Add API integration tests for both JWTs with client authentication and
without it


__NOTE:__ We're not gating this functionality with the config flag
(`xpack.security.authc.http.jwt.taggedRoutesOnly`) as we did for the
Serverless offering. It'd be a breaking change as we already implicitly
support JWT authentication without client authentication, and to be
honest, it's not really necessary anyway.

## Testing

Refer to the `Testing` section in this PR description:
#159117.

Or run already pre-configured Kibana functional test server: 
1. `node scripts/functional_tests_server.js --config
x-pack/test/security_api_integration/api_keys.config.ts`
2. Create a role mapping for JWT user:
```bash
curl -X POST --location "http://localhost:9220/_security/role_mapping/jwt" \
    -H "Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==" \
    -H "Accept: application/json" \
    -H "Content-Type: application/json" \
    -d "{
          \"roles\": [ \"superuser\" ],
          \"enabled\": true,
          \"rules\": { \"all\": [{\"field\" : { \"realm.name\" : \"jwt_with_secret\" }}] }
        }"
```
3. Send any Kibana API request with the following credentials:
```bash
curl -X POST --location "xxxx"
  -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC8iLCJzdWIiOiJlbGFzdGljLWFnZW50IiwiYXVkIjoiZWxhc3RpY3NlYXJjaCIsIm5hbWUiOiJFbGFzdGljIEFnZW50IiwiaWF0Ijo5NDY2ODQ4MDAsImV4cCI6NDA3MDkwODgwMH0.P7RHKZlLskS5DfVRqoVO4ivoIq9rXl2-GW6hhC9NvTSkwphYivcjpTVcyENZvxTTvJJNqcyx6rF3T-7otTTIHBOZIMhZauc5dob-sqcN_mT2htqm3BpSdlJlz60TBq6diOtlNhV212gQCEJMPZj0MNj7kZRj_GsECrTaU7FU0A3HAzkbdx15vQJMKZiFbbQCVI7-X2J0bZzQKIWfMHD-VgHFwOe6nomT-jbYIXtCBDd6fNj1zTKRl-_uzjVqNK-h8YW1h6tE4xvZmXyHQ1-9yNKZIWC7iEaPkBLaBKQulLU5MvW3AtVDUhzm6--5H1J85JH5QhRrnKYRon7ZW5q1AQ'
  -H 'ES-Client-Authentication: SharedSecret my_super_secret'

....for example....
curl -X GET --location "http://localhost:5620/internal/security/me" \
    -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2tpYmFuYS5lbGFzdGljLmNvL2p3dC8iLCJzdWIiOiJlbGFzdGljLWFnZW50IiwiYXVkIjoiZWxhc3RpY3NlYXJjaCIsIm5hbWUiOiJFbGFzdGljIEFnZW50IiwiaWF0Ijo5NDY2ODQ4MDAsImV4cCI6NDA3MDkwODgwMH0.P7RHKZlLskS5DfVRqoVO4ivoIq9rXl2-GW6hhC9NvTSkwphYivcjpTVcyENZvxTTvJJNqcyx6rF3T-7otTTIHBOZIMhZauc5dob-sqcN_mT2htqm3BpSdlJlz60TBq6diOtlNhV212gQCEJMPZj0MNj7kZRj_GsECrTaU7FU0A3HAzkbdx15vQJMKZiFbbQCVI7-X2J0bZzQKIWfMHD-VgHFwOe6nomT-jbYIXtCBDd6fNj1zTKRl-_uzjVqNK-h8YW1h6tE4xvZmXyHQ1-9yNKZIWC7iEaPkBLaBKQulLU5MvW3AtVDUhzm6--5H1J85JH5QhRrnKYRon7ZW5q1AQ' \
    -H 'ES-Client-Authentication: SharedSecret my_super_secret' \
    -H "Accept: application/json"
----
{
  "username": "elastic-agent",
  "roles": [
    "superuser"
  ],
  "full_name": null,
  "email": null,
  "metadata": {
    "jwt_claim_sub": "elastic-agent",
    "jwt_token_type": "access_token",
    "jwt_claim_iss": "https://kibana.elastic.co/jwt/",
    "jwt_claim_name": "Elastic Agent",
    "jwt_claim_aud": [
      "elasticsearch"
    ]
  },
  "enabled": true,
  "authentication_realm": {
    "name": "jwt_with_secret",
    "type": "jwt"
  },
  "lookup_realm": {
    "name": "jwt_with_secret",
    "type": "jwt"
  },
  "authentication_type": "realm",
  "authentication_provider": {
    "type": "http",
    "name": "__http__"
  },
  "elastic_cloud_user": false
}
```

__Fixes:__ #171522

----

Release note: The default value of the
`elasticsearch.requestHeadersWhitelist` configuration option has been
expanded to include the `es-client-authentication` HTTP header, in
addition to `authorization`.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ci:no-auto-commit Disable auto-committing changes on CI skip-ci
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant