diff --git a/.github/workflows/backport_branches.yml b/.github/workflows/backport_branches.yml
index 8437ffdf5d66..2e2b5d9eeccd 100644
--- a/.github/workflows/backport_branches.yml
+++ b/.github/workflows/backport_branches.yml
@@ -4,6 +4,12 @@ name: BackportPR
on:
workflow_dispatch:
+ inputs:
+ no_cache:
+ description: Run without cache
+ required: false
+ type: boolean
+ default: false
pull_request:
branches: ['2[1-9].[1-9][0-9]', '2[1-9].[1-9]']
@@ -11,7 +17,7 @@ env:
# Force the stdout and stderr streams to be unbuffered
PYTHONUNBUFFERED: 1
DISABLE_CI_MERGE_COMMIT: ${{ vars.DISABLE_CI_MERGE_COMMIT || '0' }}
- DISABLE_CI_CACHE: ${{ vars.DISABLE_CI_CACHE || '0' }}
+ DISABLE_CI_CACHE: ${{ github.event.inputs.no_cache || '0' }}
CHECKOUT_REF: ${{ vars.DISABLE_CI_MERGE_COMMIT == '1' && github.event.pull_request.head.sha || '' }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
CLICKHOUSE_TEST_STAT_URL: ${{ secrets.CLICKHOUSE_TEST_STAT_URL }}
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index 42c9a0c68205..c12bcc250c3e 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -4,12 +4,19 @@ name: MasterCI
on:
workflow_dispatch:
+ inputs:
+ no_cache:
+ description: Run without cache
+ required: false
+ type: boolean
+ default: false
push:
branches: ['antalya', 'releases/*', 'antalya-*']
env:
# Force the stdout and stderr streams to be unbuffered
PYTHONUNBUFFERED: 1
+ DISABLE_CI_CACHE: ${{ github.event.inputs.no_cache || '0' }}
CHECKOUT_REF: ""
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
CLICKHOUSE_TEST_STAT_URL: ${{ secrets.CLICKHOUSE_TEST_STAT_URL }}
diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml
index 65d3471f36f8..936c130ca473 100644
--- a/.github/workflows/merge_queue.yml
+++ b/.github/workflows/merge_queue.yml
@@ -348,101 +348,3 @@ jobs:
else
python3 -m praktika run 'Finish Workflow' --workflow "MergeQueueCI" --ci |& tee ./ci/tmp/job.log
fi
-
-##########################################################################################
-##################################### ALTINITY JOBS ######################################
-##########################################################################################
- GrypeScanServer:
- needs: [config_workflow, docker_server_image]
- if: ${{ !failure() && !cancelled() && !contains(fromJson(needs.config_workflow.outputs.data).cache_success_base64, 'RG9ja2VyIHNlcnZlciBpbWFnZQ==') }}
- strategy:
- fail-fast: false
- matrix:
- suffix: ['', '-alpine']
- uses: ./.github/workflows/grype_scan.yml
- secrets: inherit
- with:
- docker_image: altinityinfra/clickhouse-server
- version: ${{ fromJson(needs.config_workflow.outputs.data).custom_data.version.string }}
- tag-suffix: ${{ matrix.suffix }}
- GrypeScanKeeper:
- needs: [config_workflow, docker_keeper_image]
- if: ${{ !failure() && !cancelled() && !contains(fromJson(needs.config_workflow.outputs.data).cache_success_base64, 'RG9ja2VyIGtlZXBlciBpbWFnZQ==') }}
- uses: ./.github/workflows/grype_scan.yml
- secrets: inherit
- with:
- docker_image: altinityinfra/clickhouse-keeper
- version: ${{ fromJson(needs.config_workflow.outputs.data).custom_data.version.string }}
-
- RegressionTestsRelease:
- needs: [config_workflow, build_amd_release]
- if: ${{ !failure() && !cancelled() && !contains(github.event.pull_request.body, '[x]
+
+ default
+ default
+
+
+
diff --git a/tests/integration/test_jwt_auth/configs/validators.xml b/tests/integration/test_jwt_auth/configs/validators.xml
new file mode 100644
index 000000000000..1522937629cd
--- /dev/null
+++ b/tests/integration/test_jwt_auth/configs/validators.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ HS256
+ my_secret
+ false
+
+
+
+ hs256
+ other_secret
+ false
+
+
+
+ {"keys": [{"kty": "RSA", "alg": "rs256", "kid": "mykid", "n": "lICGC8S5pObyASih5qfmwuclG0oKsbzY2z9vgwqyhTYQOWcqYcTjVV4aQ30qb6E0-5W6rJ-jx9zx6GuAEGMiG_aWJEdbUAMGp-L1kz4lrw5U6GlwoZIvk4wqoRwsiyc-mnDMQAmiZLBNyt3wU6YnKgYmb4O1cSzcZ5HMbImJpj4tpYjqnIazvYMn_9Pxjkl0ezLCr52av0UkWHro1H4QMVfuEoNmHuWPww9jgHn-I-La0xdOhRpAa0XnJi65dXZd4330uWjeJwt413yz881uS4n1OLOGKG8ImDcNlwU_guyvk0n0aqT0zkOAPp9_yYo13MPWmiRCfOX8ozdN7VDIJw", "e": "AQAB"}]}
+
+
+
+ http://resolver:8080/.well-known/jwks.json
+
+
+
diff --git a/tests/integration/test_jwt_auth/helpers/generate_private_key.py b/tests/integration/test_jwt_auth/helpers/generate_private_key.py
new file mode 100644
index 000000000000..7b54fa63368b
--- /dev/null
+++ b/tests/integration/test_jwt_auth/helpers/generate_private_key.py
@@ -0,0 +1,21 @@
+from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.backends import default_backend
+
+# Generate RSA private key
+private_key = rsa.generate_private_key(
+ public_exponent=65537,
+ key_size=2048, # Key size of 2048 bits
+ backend=default_backend()
+)
+
+# Save the private key to a PEM file
+pem_private_key = private_key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
+ encryption_algorithm=serialization.NoEncryption() # You can add encryption if needed
+)
+
+# Write the private key to a file
+with open("new_private_key", "wb") as pem_file:
+ pem_file.write(pem_private_key)
diff --git a/tests/integration/test_jwt_auth/helpers/jwt_jwk.py b/tests/integration/test_jwt_auth/helpers/jwt_jwk.py
new file mode 100644
index 000000000000..265882efce76
--- /dev/null
+++ b/tests/integration/test_jwt_auth/helpers/jwt_jwk.py
@@ -0,0 +1,113 @@
+from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.hazmat.primitives import serialization
+
+import base64
+import json
+import jwt
+
+
+"""
+Only RS* family algorithms are supported!!!
+"""
+with open("./private_key_2", "rb") as key_file:
+ private_key = serialization.load_pem_private_key(
+ key_file.read(),
+ password=None,
+ )
+
+
+public_key = private_key.public_key()
+
+
+def to_base64_url(data):
+ return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
+
+
+def rsa_key_to_jwk(private_key=None, public_key=None):
+ if private_key:
+ # Convert the private key to its components
+ private_numbers = private_key.private_numbers()
+ public_numbers = private_key.public_key().public_numbers()
+
+ jwk = {
+ "kty": "RSA",
+ "alg": "RS512",
+ "kid": "mykid",
+ "n": to_base64_url(
+ public_numbers.n.to_bytes(
+ (public_numbers.n.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "e": to_base64_url(
+ public_numbers.e.to_bytes(
+ (public_numbers.e.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "d": to_base64_url(
+ private_numbers.d.to_bytes(
+ (private_numbers.d.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "p": to_base64_url(
+ private_numbers.p.to_bytes(
+ (private_numbers.p.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "q": to_base64_url(
+ private_numbers.q.to_bytes(
+ (private_numbers.q.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "dp": to_base64_url(
+ private_numbers.dmp1.to_bytes(
+ (private_numbers.dmp1.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "dq": to_base64_url(
+ private_numbers.dmq1.to_bytes(
+ (private_numbers.dmq1.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "qi": to_base64_url(
+ private_numbers.iqmp.to_bytes(
+ (private_numbers.iqmp.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ }
+ elif public_key:
+ # Convert the public key to its components
+ public_numbers = public_key.public_numbers()
+
+ jwk = {
+ "kty": "RSA",
+ "alg": "RS512",
+ "kid": "mykid",
+ "n": to_base64_url(
+ public_numbers.n.to_bytes(
+ (public_numbers.n.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ "e": to_base64_url(
+ public_numbers.e.to_bytes(
+ (public_numbers.e.bit_length() + 7) // 8, byteorder="big"
+ )
+ ),
+ }
+ else:
+ raise ValueError("You must provide either a private or public key.")
+
+ return jwk
+
+
+# Convert to JWK
+jwk_private = rsa_key_to_jwk(private_key=private_key)
+jwk_public = rsa_key_to_jwk(public_key=public_key)
+
+print(f"Private JWK:\n{json.dumps(jwk_private)}\n")
+print(f"Public JWK:\n{json.dumps(jwk_public)}\n")
+
+payload = {"sub": "jwt_user", "iss": "test_iss"}
+
+# Create a JWT
+token = jwt.encode(payload, private_key, headers={"kid": "mykid"}, algorithm="RS512")
+print(f"JWT:\n{token}")
diff --git a/tests/integration/test_jwt_auth/helpers/jwt_static_secret.py b/tests/integration/test_jwt_auth/helpers/jwt_static_secret.py
new file mode 100644
index 000000000000..5f1c7e0340af
--- /dev/null
+++ b/tests/integration/test_jwt_auth/helpers/jwt_static_secret.py
@@ -0,0 +1,43 @@
+import jwt
+import datetime
+
+
+def create_jwt(
+ payload: dict, secret: str, algorithm: str = "HS256", expiration_minutes: int = None
+) -> str:
+ """
+ Create a JWT using a static secret and a specified encryption algorithm.
+
+ :param payload: The payload to include in the JWT (as a dictionary).
+ :param secret: The secret key used to sign the JWT.
+ :param algorithm: The encryption algorithm to use (default is 'HS256').
+ :param expiration_minutes: The time until the token expires (default is 60 minutes).
+ :return: The encoded JWT as a string.
+ """
+ if expiration_minutes:
+ expiration = datetime.datetime.utcnow() + datetime.timedelta(
+ minutes=expiration_minutes
+ )
+ payload["exp"] = expiration
+
+ return jwt.encode(payload, secret, algorithm=algorithm)
+
+
+if __name__ == "__main__":
+ secret = "my_secret"
+ payload = {"sub": "jwt_user"} # `sub` must contain user name
+
+ """
+ Supported algorithms:
+ | HMSC | RSA | ECDSA | PSS | EdDSA |
+ | ----- | ----- | ------ | ----- | ------- |
+ | HS256 | RS256 | ES256 | PS256 | Ed25519 |
+ | HS384 | RS384 | ES384 | PS384 | Ed448 |
+ | HS512 | RS512 | ES512 | PS512 | |
+ | | | ES256K | | |
+ And None
+ """
+ algorithm = "HS256"
+
+ token = create_jwt(payload, secret, algorithm)
+ print(f"Generated JWT: {token}")
diff --git a/tests/integration/test_jwt_auth/helpers/private_key_1 b/tests/integration/test_jwt_auth/helpers/private_key_1
new file mode 100644
index 000000000000..a076a86e17a4
--- /dev/null
+++ b/tests/integration/test_jwt_auth/helpers/private_key_1
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAlICGC8S5pObyASih5qfmwuclG0oKsbzY2z9vgwqyhTYQOWcq
+YcTjVV4aQ30qb6E0+5W6rJ+jx9zx6GuAEGMiG/aWJEdbUAMGp+L1kz4lrw5U6Glw
+oZIvk4wqoRwsiyc+mnDMQAmiZLBNyt3wU6YnKgYmb4O1cSzcZ5HMbImJpj4tpYjq
+nIazvYMn/9Pxjkl0ezLCr52av0UkWHro1H4QMVfuEoNmHuWPww9jgHn+I+La0xdO
+hRpAa0XnJi65dXZd4330uWjeJwt413yz881uS4n1OLOGKG8ImDcNlwU/guyvk0n0
+aqT0zkOAPp9/yYo13MPWmiRCfOX8ozdN7VDIJwIDAQABAoIBADZfiLUuZrrWRK3f
+7sfBmmCquY9wYNILT2uXooDcndjgnrgl6gK6UHKlbgBgB/WvlPK5NAyYtyMq5vgu
+xEk7wvVyKC9IYUq+kOVP2JL9IlcibDxcvvypxfnETKeI5VZeHDH4MxEPdgJf+1vY
+P3KhV52vestB8mFqB5l0bOEgyuGvO3/3D1JjOnFLS/K2vOj8D/KDRmwXRCcGHTxj
+dj3wJH4UbCIsLgiaQBPkFmTteJDICb+7//6YQuB0t8sR/DZS9Z0GWcfy04Cp/m/E
+4rRoTNz80MbbU9+k0Ly360SxPizcjpPYSRSD025i8Iqv8jvelq7Nzg69Kubc0KfN
+mMrRdMECgYEAz4b7+OX+aO5o2ZQS+fHc8dyWc5umC+uT5xrUm22wZLYA5O8x0Rgj
+vdO/Ho/XyN/GCyvNNV2rI2+CBTxez6NqesGDEmJ2n7TQ03xXLCVsnwVz694sPSMO
+pzTbU6e42jvDo5DMPDv0Pg1CVQuM9ka6wb4DcolMyDql6QddY3iXHBkCgYEAtzAl
+xEAABqdFAnCs3zRf9EZphGJiJ4gtoWmCxQs+IcrfyBNQCy6GqrzJOZ7fQiEoAeII
+V0JmsNcnx3U1W0lp8N+1QNZoB4fOWXaX08BvOEe7gbJ6Xl5t52j792vQp1txpBhE
+UDhz8m5R9i5qb3BzrYBiSTfak0Pq56Xw3jRDjj8CgYEAqX2QS07kQqT8gz85ZGOR
+1QMY6aCks7WaXTR/kdW7K/Wts0xb/m7dugq3W+mVDh0c7UC/36b5v/4xTb9pm+HW
+dB2ZxCkgwvz1VNSHiamjFhlo/Km+rcv1CsDTpHYmNi57cRowg71flFJV64l8fiN0
+IgnjXOcgC6RCnpiCQFxb5fkCgYB+Zq2YleSuspqOjXrrZPNU1YUXgN9jkbaSqwA9
+wH01ygvRvWm83XS0uSFMLhC1S7WUXwgMVdgP69YZ7glMHQMJ3wLtY0RS9eVvm8I1
+rZHQzsZWPvXqydOiGrHJzs4hvJpUdR4mEF4JCRBrAyoUDQ70yCKJjQ24EeQzxS/H
+015N9wKBgB8DdFPvKXyygTMnBoZdpAhkE/x3TTi7DsLBxj7QxKmSHzlHGz0TubIB
+m5/p9dGawQNzD4JwASuY5r4lKXmvYr+4TQPLq6c7EnoIZSwLdge+6PDhnDWJzvk1
+S/RuHWW4FKGzBStTmstG3m0xzxTMnQkV3kPimMim3I3VsxxeGEdq
+-----END RSA PRIVATE KEY-----
diff --git a/tests/integration/test_jwt_auth/helpers/private_key_2 b/tests/integration/test_jwt_auth/helpers/private_key_2
new file mode 100644
index 000000000000..d0d1576f2017
--- /dev/null
+++ b/tests/integration/test_jwt_auth/helpers/private_key_2
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA0RRsKcZ5j9UckjioG4Phvav3dkg2sXP6tQ7ug0yowAo/u2Hf
+fB+1OjKuhWTpA3E3YkMKj0RrT+tuUpmZEXqCAipEV7XcfCv3o7Poa7HTq1ti/abV
+wT/KyfGjoNBBSJH4LTNAyo2J8ySKSDtpAEU52iL7s40Ra6I0vqp7/aRuPF5M4zcH
+zN3zarG5EfSVSG1+gTkaRv8XJbra0IeIINmKv0F4++ww8ZxXTR6cvI+MsArUiAPw
+zf7s5dMR4DNRG6YNTrPA0pTOqQE9sRPd62XsfU08plYm27naOUZO5avIPl1YO5I6
+Gi4kPdTvv3WFIy+QvoKoPhPCaD6EbdBpe8BbTQIDAQABAoIBABghJsCFfucKHdOE
+RWZziHx22cblW6aML41wzTcLBFixdhx+lafCEwzF551OgZPbn5wwB4p0R3xAPAm9
+X0yEmnd8gEmtG+aavmg+rZ6sNbULhXenpvi4D4PR5uP61OX2rrEsvpgB0L9mYq0m
+ah5VXvFdYzYcHDwTSsoMa+XgcbZ2qCW6Si3jnbBA1TPIJS5GjfPUQlu9g2FKQL5H
+tlJ7L4Wq39zkueS6LH7kEXOoM+jHgA8F4f7MIrajmilYqnuXanVcMV3+K/6FvH2B
+VBiLggG3CerhB3QyEvZBshvEvvcyRff2NK64CGr/xrAElj4cPHk/E499M1uvUXjE
+boCrD+ECgYEA9LvLljf59h8WWF4bKQZGNKprgFdQIZ2iCEf+VGdGWt/mNg+LyXyn
+3gS/vReON1eaMuEGklZM4Guh/ZPhsPaNmlu16PjmeYTIW1vQTHiO3KR7tAmWep70
+w+gVxDDzuRvBkuDF5oQsZnD3Ri9I7r+J5y9OhyZUsDXe/LJARivF3x0CgYEA2rRx
+wl4mfuYmikvcO8I4vuKXcK1UyYmZQLhp6EHKfhSVgrt7XsstZX9AP2OxUUAocRks
+e6vU/sKUSni7TQrZzAZHc8JXonDgmCqoMPBXIuUncvysGR1kmgVIbN8ISPKJuZoV
+8Dbj3fQfHZ0g0R+mUcuZ+xBO5CKcjPWHZXZoxfECgYAQ/5o8bNbnyXD74k1wpAbs
+UYn1+BqQuyot+RIpOqMgXLzYtGu5Kvdd7GaE88XlAiirsAWM1IGydMdjnYnniLh9
+KDGSZPddKWPhNJdbOGRz3tjYwHG7Qp8tnEkmv1+uU8c2NHaKdFPBKceDEHW4X4Vs
+kVSa/oaTVqqOUrM0LIYp4QKBgQCW1aIriiGEnZhxAvbGJCJczAvkAzcZtBOFBmrM
+ayuLnwiqXEEu1HPfr06RKWFuhxAdSF5cgNrqRSpe3jtXXCdvxdjbpmooNy8+4xSS
+g/+kqmR1snvC6nmqnAAiTgP5w4RnBDUjMcggGLCpDOhIMkrT2Na+x7WRM6nCsceK
+m4qREQKBgEWqdb/QkOMvvKAz2DPDeSrwlTyisrZu1G/86uE3ESb97DisPK+TF2Ts
+r4RGUlKL79W3j5xjvIvqGEEDLC+8QKpay9OYXk3lbViPGB8akWMSP6Tw/8AedhVu
+sjFqcBEFGOELwm7VjAcDeP6bXeXibFe+rysBrfFHUGllytCmNoAV
+-----END RSA PRIVATE KEY-----
diff --git a/tests/integration/test_jwt_auth/jwks_server/server.py b/tests/integration/test_jwt_auth/jwks_server/server.py
new file mode 100644
index 000000000000..96e07f02335e
--- /dev/null
+++ b/tests/integration/test_jwt_auth/jwks_server/server.py
@@ -0,0 +1,33 @@
+import sys
+
+from bottle import response, route, run
+
+
+@route("/.well-known/jwks.json")
+def server():
+ result = {
+ "keys": [
+ {
+ "kty": "RSA",
+ "alg": "RS512",
+ "kid": "mykid",
+ "n": "0RRsKcZ5j9UckjioG4Phvav3dkg2sXP6tQ7ug0yowAo_u2HffB-1OjKuhWTpA3E3YkMKj0RrT-tuUpmZEXqCAipEV7XcfCv3o"
+ "7Poa7HTq1ti_abVwT_KyfGjoNBBSJH4LTNAyo2J8ySKSDtpAEU52iL7s40Ra6I0vqp7_aRuPF5M4zcHzN3zarG5EfSVSG1-gT"
+ "kaRv8XJbra0IeIINmKv0F4--ww8ZxXTR6cvI-MsArUiAPwzf7s5dMR4DNRG6YNTrPA0pTOqQE9sRPd62XsfU08plYm27naOUZ"
+ "O5avIPl1YO5I6Gi4kPdTvv3WFIy-QvoKoPhPCaD6EbdBpe8BbTQ",
+ "e": "AQAB"},
+ ]
+ }
+ response.status = 200
+ response.content_type = "application/json"
+ return result
+
+
+@route("/")
+def ping():
+ response.content_type = "text/plain"
+ response.set_header("Content-Length", 2)
+ return "OK"
+
+
+run(host="0.0.0.0", port=int(sys.argv[1]))
diff --git a/tests/integration/test_jwt_auth/test.py b/tests/integration/test_jwt_auth/test.py
new file mode 100644
index 000000000000..6a1e1fe68e72
--- /dev/null
+++ b/tests/integration/test_jwt_auth/test.py
@@ -0,0 +1,101 @@
+import os
+import pytest
+
+from helpers.cluster import ClickHouseCluster
+from helpers.mock_servers import start_mock_servers
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+cluster = ClickHouseCluster(__file__)
+instance = cluster.add_instance(
+ "instance",
+ main_configs=["configs/validators.xml"],
+ user_configs=["configs/users.xml"],
+ with_minio=True,
+ # We actually don't need minio, but we need to run dummy resolver
+ # (a shortcut not to change cluster.py in a more unclear way, TBC later).
+)
+client = cluster.add_instance(
+ "client",
+)
+
+
+def run_jwks_server():
+ script_dir = os.path.join(os.path.dirname(__file__), "jwks_server")
+ start_mock_servers(
+ cluster,
+ script_dir,
+ [
+ ("server.py", "resolver", "8080"),
+ ],
+ )
+
+
+@pytest.fixture(scope="module")
+def started_cluster():
+ try:
+ cluster.start()
+ run_jwks_server()
+ yield cluster
+ finally:
+ cluster.shutdown()
+
+
+def curl_with_jwt(token, ip, https=False):
+ http_prefix = "https" if https else "http"
+ curl = f'curl -H "X-ClickHouse-JWT-Token: Bearer {token}" "{http_prefix}://{ip}:8123/?query=SELECT%20currentUser()"'
+ return curl
+
+
+# See helpers/ directory if you need to re-create tokens (or understand how they are created)
+def test_static_key(started_cluster):
+ res = client.exec_in_container(
+ [
+ "bash",
+ "-c",
+ curl_with_jwt(
+ token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqd3RfdXNlciJ9."
+ "kfivQ8qD_oY0UvihydeadD7xvuiO3zSmhFOc_SGbEPQ",
+ ip=cluster.get_instance_ip(instance.name),
+ ),
+ ]
+ )
+ assert res == "jwt_user\n"
+
+
+def test_static_jwks(started_cluster):
+ res = client.exec_in_container(
+ [
+ "bash",
+ "-c",
+ curl_with_jwt(
+ token="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im15a2lkIn0."
+ "eyJzdWIiOiJqd3RfdXNlciIsImlzcyI6InRlc3RfaXNzIn0."
+ "CUioyRc_ms75YWkUwvPgLvaVk2Wmj8RzgqDALVd9LWUzCL5aU4yc_YaA3qnG_NoHd0uUF4FUjLxiocRoKNEgsE2jj7g_"
+ "wFMC5XHSHuFlfIZjovObXQEwGcKpXO2ser7ANu3k2jBC2FMpLfr_sZZ_GYSnqbp2WF6-l0uVQ0AHVwOy4x1Xkawiubkg"
+ "W2I2IosaEqT8QNuvvFWLWc1k-dgiNp8k6P-K4D4NBQub0rFlV0n7AEKNdV-_AEzaY_IqQT0sDeBSew_mdR0OH_N-6-"
+ "FmWWIroIn2DQ7pq93BkI7xdkqnxtt8RCWkCG8JLcoeJt8sHh7uTKi767loZJcPPNaxKA",
+ ip=cluster.get_instance_ip(instance.name),
+ ),
+ ]
+ )
+ assert res == "jwt_user\n"
+
+
+def test_jwks_server(started_cluster):
+ res = client.exec_in_container(
+ [
+ "bash",
+ "-c",
+ curl_with_jwt(
+ token="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiIsImtpZCI6Im15a2lkIn0."
+ "eyJzdWIiOiJqd3RfdXNlciIsImlzcyI6InRlc3RfaXNzIn0.MjegqrrVyrMMpkxIM-J_q-"
+ "Sw68Vk5xZuFpxecLLMFs5qzvnh0jslWtyRfi-ANJeJTONPZM5m0yP1ITt8BExoHWobkkR11bXz0ylYEIOgwxqw"
+ "36XhL2GkE17p-wMvfhCPhGOVL3b7msDRUKXNN48aAJA-NxRbQFhMr-eEx3HsrZXy17Qc7z-"
+ "0dINe355kzAInGp6gMk3uksAlJ3vMODK8jE-WYFqXusr5GFhXubZXdE2mK0mIbMUGisOZhZLc4QVwvUsYDLBCgJ2RHr5vm"
+ "jp17j_ZArIedUJkjeC4o72ZMC97kLVnVw94QJwNvd4YisxL6A_mWLTRq9FqNLD4HmbcOQ",
+ ip=cluster.get_instance_ip(instance.name),
+ ),
+ ]
+ )
+ assert res == "jwt_user\n"