Skip to content

Commit

Permalink
port asymmetric algo jwt support to 3.3.x
Browse files Browse the repository at this point in the history
  • Loading branch information
ancalita committed Dec 6, 2022
1 parent f46628c commit 034c6dc
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 2 deletions.
1 change: 1 addition & 0 deletions .typo-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,6 @@ excluded_words:
- Juste
- Tanja
- Vova
- conftest

spellcheck_filenames: false
12 changes: 12 additions & 0 deletions docs/docs/http-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ rasa run \
--jwt-secret thisismysecret
```

If you want to sign a JWT token with asymmetric algorithms, you can specify the JWT private key to the `--jwt-private-key`
CLI argument. You must pass the public key to the `--jwt-secret` argument, and also specify the algorithm to the
`--jwt-method` argument:

```bash
rasa run \
--enable-api \
--jwt-secret <public_key> \
--jwt-private-key <private_key> \
--jwt-method RS512
```

Client requests to the server will need to contain a valid JWT token in
the `Authorization` header that is signed using this secret
and the `HS256` algorithm e.g.
Expand Down
7 changes: 7 additions & 0 deletions rasa/cli/arguments/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,10 @@ def add_server_arguments(parser: argparse.ArgumentParser) -> None:
default="HS256",
help="Method used for the signature of the JWT authentication payload.",
)
jwt_auth.add_argument(
"--jwt-private-key",
type=str,
help="A private key used for generating web tokens, dependent upon "
"which hashing algorithm is used. It must be used together with "
"--jwt-secret for providing the public key.",
)
4 changes: 4 additions & 0 deletions rasa/core/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def configure_app(
enable_api: bool = True,
response_timeout: int = constants.DEFAULT_RESPONSE_TIMEOUT,
jwt_secret: Optional[Text] = None,
jwt_private_key: Optional[Text] = None,
jwt_method: Optional[Text] = None,
route: Optional[Text] = "/webhooks/",
port: int = constants.DEFAULT_SERVER_PORT,
Expand All @@ -106,6 +107,7 @@ def configure_app(
auth_token=auth_token,
response_timeout=response_timeout,
jwt_secret=jwt_secret,
jwt_private_key=jwt_private_key,
jwt_method=jwt_method,
endpoints=endpoints,
)
Expand Down Expand Up @@ -157,6 +159,7 @@ def serve_application(
enable_api: bool = True,
response_timeout: int = constants.DEFAULT_RESPONSE_TIMEOUT,
jwt_secret: Optional[Text] = None,
jwt_private_key: Optional[Text] = None,
jwt_method: Optional[Text] = None,
endpoints: Optional[AvailableEndpoints] = None,
remote_storage: Optional[Text] = None,
Expand Down Expand Up @@ -185,6 +188,7 @@ def serve_application(
enable_api,
response_timeout,
jwt_secret,
jwt_private_key,
jwt_method,
port=port,
endpoints=endpoints,
Expand Down
4 changes: 3 additions & 1 deletion rasa/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ def create_app(
auth_token: Optional[Text] = None,
response_timeout: int = DEFAULT_RESPONSE_TIMEOUT,
jwt_secret: Optional[Text] = None,
jwt_private_key: Optional[Text] = None,
jwt_method: Text = "HS256",
endpoints: Optional[AvailableEndpoints] = None,
) -> Sanic:
Expand All @@ -653,7 +654,7 @@ def create_app(
app.config.RESPONSE_TIMEOUT = response_timeout
configure_cors(app, cors_origins)

# Setup the Sanic-JWT extension
# Set up the Sanic-JWT extension
if jwt_secret and jwt_method:
# `sanic-jwt` depends on having an available event loop when making the call to
# `Initialize`. If there is none, the server startup will fail with
Expand All @@ -671,6 +672,7 @@ def create_app(
Initialize(
app,
secret=jwt_secret,
private_key=jwt_private_key,
authenticate=authenticate,
algorithm=jwt_method,
user_id="username",
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/test_rasa_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_run_help(
[--ssl-keyfile SSL_KEYFILE] [--ssl-ca-file SSL_CA_FILE]
[--ssl-password SSL_PASSWORD] [--credentials CREDENTIALS]
[--connector CONNECTOR] [--jwt-secret JWT_SECRET]
[--jwt-method JWT_METHOD]
[--jwt-method JWT_METHOD] [--jwt-private-key JWT_PRIVATE_KEY]
{actions} ... [model-as-positional-argument]"""
)

Expand Down
68 changes: 68 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import random
import textwrap

import jwt
import pytest
import sys
import uuid
Expand Down Expand Up @@ -489,6 +490,73 @@ def rasa_server_secured(default_agent: Agent) -> Sanic:
return app


@pytest.fixture
def test_public_key() -> Text:
test_public_key = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC34ht9inqGq79HecpyOAnu2Cgv
jvgcpFifpFLPmCNdiomAgE48tfUAXJRoOGlVtrqc8KgQWjTFLjqDjUh1sBFF69Fl
wQGt7pgH10ZbERWpMTAbpjI9EoH74gDcmZ6Fy1VgQPbAwty3liw5Q5zqZLj7JhuX
Sa0EqvZQP+Hnayab7QIDAQAB
-----END PUBLIC KEY-----"""

return test_public_key


@pytest.fixture
def test_private_key() -> Text:
test_private_key = """-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC34ht9inqGq79HecpyOAnu2CgvjvgcpFifpFLPmCNdiomAgE48
tfUAXJRoOGlVtrqc8KgQWjTFLjqDjUh1sBFF69FlwQGt7pgH10ZbERWpMTAbpjI9
EoH74gDcmZ6Fy1VgQPbAwty3liw5Q5zqZLj7JhuXSa0EqvZQP+Hnayab7QIDAQAB
AoGBAIfUE25mjh9QWljX0/0O+/db4ENRHmE53OT/otQJk4YTQYKURDaASdvchxt9
IAHamno3Ik4B9Bz7CuoFwNJ+HiMBf32KwJ75n/NZL17lBKst71z3r0gYCz6jcJxv
brbNs8qsLFyRMQz6NvS4d4GnXpGhc54IoJqtr/vR+Q87UwtZAkEA3AG78E7Fd5zT
sU/BO9E0VisQOysGcwPd9+rQPSyF8ncvaiMJ7STNvVsgrtJuw4DJq2RsMSJ77QgS
Ku6BJxB58wJBANX3dOEiNEZLJR+4LdNYRoR4gx2LcJW5PthwLi8ZOHBZeh9q3f2i
r5X5iPJ5kBRqajtYm634f/j8P4fxSdWzKp8CQQCNimQR92udR3z+HxRvWml0YmIf
3s9YYY2FeUEdii5mznznqMEzGzFt+Fmvf1yZVJrqNEJS3h+iYEXn7ueSbUw3AkBm
xSK4d+tP0AwWvioUlxPX0OJ5MF51K7LJ1qf4K072d6O2r2fMyXU4vdBPVqAjjjFU
K+0qlG8zMkV5kCV8pT/VAkA8bM5KRa73JY0bfGX4i8UZMFHzIq2KGjHlRES4vd+L
h18+hpcBAAyUR/jDT8nnG5YaYFz8rf2DnOy+elmmaYVm
-----END RSA PRIVATE KEY-----"""

return test_private_key


@pytest.fixture
def asymmetric_jwt_method() -> Text:
return "RS256"


@pytest.fixture
def rasa_server_secured_asymmetric(
default_agent: Agent,
test_public_key: Text,
test_private_key: Text,
asymmetric_jwt_method: Text,
) -> Sanic:
app = server.create_app(
agent=default_agent,
auth_token="rasa",
jwt_secret=test_public_key,
jwt_private_key=test_private_key,
jwt_method=asymmetric_jwt_method,
)
channel.register([RestInput()], app, "/webhooks/")
return app


@pytest.fixture
def encoded_jwt(test_private_key: Text, asymmetric_jwt_method: Text) -> Text:
payload = {"user": {"username": "myuser", "role": "admin"}}
encoded_jwt = jwt.encode(
payload=payload,
key=test_private_key,
algorithm=asymmetric_jwt_method,
)
return encoded_jwt


@pytest.fixture
def rasa_non_trained_server_secured(empty_agent: Agent) -> Sanic:
app = server.create_app(agent=empty_agent, auth_token="rasa", jwt_secret="core")
Expand Down
23 changes: 23 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ def rasa_secured_app(rasa_server_secured: Sanic) -> SanicASGITestClient:
return rasa_server_secured.asgi_client


@pytest.fixture
def rasa_secured_app_asymmetric(
rasa_server_secured_asymmetric: Sanic,
) -> SanicASGITestClient:
return rasa_server_secured_asymmetric.asgi_client


@pytest.fixture
def rasa_non_trained_secured_app(
rasa_non_trained_server_secured: Sanic,
Expand Down Expand Up @@ -1374,6 +1381,22 @@ async def test_get_tracker_with_jwt(rasa_secured_app: SanicASGITestClient):
assert response.status == HTTPStatus.OK


async def test_get_tracker_with_asymmetric_jwt(
rasa_secured_app_asymmetric: SanicASGITestClient,
encoded_jwt: Text,
) -> None:
jwt_header = {"Authorization": f"Bearer {encoded_jwt}"}
_, response = await rasa_secured_app_asymmetric.get(
"/conversations/myuser/tracker", headers=jwt_header
)
assert response.status == HTTPStatus.OK

_, response = await rasa_secured_app_asymmetric.get(
"/conversations/testuser/tracker", headers=jwt_header
)
assert response.status == HTTPStatus.OK


def test_list_routes(empty_agent: Agent):
app = rasa.server.create_app(empty_agent, auth_token=None)

Expand Down
3 changes: 3 additions & 0 deletions trivy-secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ allow-rules:
- id: docs/docs/deploy/deploy-rasa.mdx
description: Example service account in docs
path: docs/docs/deploy/deploy-rasa.mdx
- id: tests/conftest.py
description: JWT private key used in unit testing
path: tests/conftest.py

0 comments on commit 034c6dc

Please sign in to comment.