Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docker-compose-library.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,32 @@ services:
- WATSONX_API_KEY=${WATSONX_API_KEY:-}
# Enable debug logging if needed
- LLAMA_STACK_LOGGING=${LLAMA_STACK_LOGGING:-}
networks:
- lightspeednet
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/liveness"]
interval: 10s # how often to run the check
timeout: 5s # how long to wait before considering it failed
retries: 3 # how many times to retry before marking as unhealthy
start_period: 15s # time to wait before starting checks (increased for library initialization)

# Mock JWKS server for RBAC E2E tests
mock-jwks:
build:
context: ./tests/e2e/mock_jwks_server
dockerfile: Dockerfile
container_name: mock-jwks
ports:
- "8000:8000"
networks:
- lightspeednet
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 5s
timeout: 3s
retries: 3
start_period: 2s

networks:
lightspeednet:
driver: bridge
27 changes: 24 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ services:
ports:
- "8321:8321" # Expose llama-stack on 8321 (adjust if needed)
volumes:
- ./run.yaml:/opt/app-root/run.yaml:Z
- ./run.yaml:/opt/app-root/run.yaml:z
- ${GCP_KEYS_PATH:-./tmp/.gcp-keys-dummy}:/opt/app-root/.gcp-keys:ro
- ./tests/e2e/rag:/opt/app-root/src/.llama/storage/rag:Z
- ./lightspeed-stack.yaml:/opt/app-root/lightspeed-stack.yaml:z
- ./lightspeed-stack.yaml:/opt/app-root/lightspeed-stack.yaml:ro
- llama-storage:/opt/app-root/src/.llama/storage
- ./tests/e2e/rag:/opt/app-root/src/.llama/storage/rag:z
environment:
- BRAVE_SEARCH_API_KEY=${BRAVE_SEARCH_API_KEY:-}
- TAVILY_SEARCH_API_KEY=${TAVILY_SEARCH_API_KEY:-}
Expand Down Expand Up @@ -78,6 +79,26 @@ services:
retries: 3 # how many times to retry before marking as unhealthy
start_period: 5s # time to wait before starting checks

# Mock JWKS server for RBAC E2E tests
mock-jwks:
build:
context: ./tests/e2e/mock_jwks_server
dockerfile: Dockerfile
container_name: mock-jwks
ports:
- "8000:8000"
networks:
- lightspeednet
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 5s
timeout: 3s
retries: 3
start_period: 2s

volumes:
llama-storage:

networks:
lightspeednet:
driver: bridge
94 changes: 94 additions & 0 deletions tests/e2e/configuration/library-mode/lightspeed-stack-rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Lightspeed Core Service (RBAC E2E Tests - Library Mode)
service:
host: 0.0.0.0
port: 8080
auth_enabled: true
workers: 1
color_log: true
access_log: true

llama_stack:
use_as_library_client: true
library_client_config_path: run.yaml

user_data_collection:
feedback_enabled: true
feedback_storage: "/tmp/data/feedback"
transcripts_enabled: true
transcripts_storage: "/tmp/data/transcripts"

# Conversation cache for storing Q&A history
conversation_cache:
type: "sqlite"
sqlite:
db_path: "/tmp/data/conversation-cache.db"

# JWK token authentication with role extraction
authentication:
module: "jwk-token"
jwk_config:
url: "http://mock-jwks:8000/.well-known/jwks.json"
jwt_configuration:
user_id_claim: "sub"
username_claim: "name"
# Role rules: extract roles from JWT claims
role_rules:
# Grant 'admin' role to users with admin=true in JWT
- jsonpath: "$.admin"
operator: "equals"
value: [true]
roles: ["admin"]
# Grant 'user' role to users with role=user in JWT
- jsonpath: "$.role"
operator: "equals"
value: ["user"]
roles: ["user"]
# Grant 'viewer' role to users with role=viewer in JWT
- jsonpath: "$.role"
operator: "equals"
value: ["viewer"]
roles: ["viewer"]
# Grant 'query_only' role based on permissions array containing 'query'
- jsonpath: "$.permissions[*]"
operator: "contains"
value: "query"
roles: ["query_only"]

# Authorization: map roles to actions
authorization:
access_rules:
# Admin role gets full access
- role: "admin"
actions: ["admin"]
# User role can query, access conversations, and provide feedback
- role: "user"
actions:
- "query"
- "streaming_query"
- "get_conversation"
- "list_conversations"
- "delete_conversation"
- "update_conversation"
- "feedback"
- "get_models"
- "get_tools"
- "info"
- "model_override"
# Viewer role can only read (no mutations)
- role: "viewer"
actions:
- "get_conversation"
- "list_conversations"
- "get_models"
- "get_tools"
- "info"
# Query-only role can only query (no model_override - must use defaults)
- role: "query_only"
actions:
- "query"
- "streaming_query"
# Everyone (*) role gets basic info access
- role: "*"
actions:
- "info"

95 changes: 95 additions & 0 deletions tests/e2e/configuration/server-mode/lightspeed-stack-rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Lightspeed Core Service (RBAC E2E Tests)
service:
host: 0.0.0.0
port: 8080
auth_enabled: true
workers: 1
color_log: true
access_log: true

llama_stack:
use_as_library_client: false
url: http://llama-stack:8321
api_key: xyzzy

user_data_collection:
feedback_enabled: true
feedback_storage: "/tmp/data/feedback"
transcripts_enabled: true
transcripts_storage: "/tmp/data/transcripts"

# Conversation cache for storing Q&A history
conversation_cache:
type: "sqlite"
sqlite:
db_path: "/tmp/data/conversation-cache.db"

# JWK token authentication with role extraction
authentication:
module: "jwk-token"
jwk_config:
url: "http://mock-jwks:8000/.well-known/jwks.json"
jwt_configuration:
user_id_claim: "sub"
username_claim: "name"
# Role rules: extract roles from JWT claims
role_rules:
# Grant 'admin' role to users with admin=true in JWT
- jsonpath: "$.admin"
operator: "equals"
value: [true]
roles: ["admin"]
# Grant 'user' role to users with role=user in JWT
- jsonpath: "$.role"
operator: "equals"
value: ["user"]
roles: ["user"]
# Grant 'viewer' role to users with role=viewer in JWT
- jsonpath: "$.role"
operator: "equals"
value: ["viewer"]
roles: ["viewer"]
# Grant 'query_only' role based on permissions array containing 'query'
- jsonpath: "$.permissions[*]"
operator: "contains"
value: "query"
roles: ["query_only"]

# Authorization: map roles to actions
authorization:
access_rules:
# Admin role gets full access
- role: "admin"
actions: ["admin"]
# User role can query, access conversations, and provide feedback
- role: "user"
actions:
- "query"
- "streaming_query"
- "get_conversation"
- "list_conversations"
- "delete_conversation"
- "update_conversation"
- "feedback"
- "get_models"
- "get_tools"
- "info"
- "model_override"
# Viewer role can only read (no mutations)
- role: "viewer"
actions:
- "get_conversation"
- "list_conversations"
- "get_models"
- "get_tools"
- "info"
# Query-only role can only query (no model_override - must use defaults)
- role: "query_only"
actions:
- "query"
- "streaming_query"
# Everyone (*) role gets basic info access
- role: "*"
actions:
- "info"

14 changes: 14 additions & 0 deletions tests/e2e/features/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ def before_feature(context: Context, feature: Feature) -> None:
switch_config(context.feature_config)
restart_container("lightspeed-stack")

if "RBAC" in feature.tags:
mode_dir = "library-mode" if context.is_library_mode else "server-mode"
context.feature_config = (
f"tests/e2e/configuration/{mode_dir}/lightspeed-stack-rbac.yaml"
)
context.default_config_backup = create_config_backup("lightspeed-stack.yaml")
switch_config(context.feature_config)
restart_container("lightspeed-stack")

if "Feedback" in feature.tags:
context.hostname = os.getenv("E2E_LSC_HOSTNAME", "localhost")
context.port = os.getenv("E2E_LSC_PORT", "8080")
Expand All @@ -259,6 +268,11 @@ def after_feature(context: Context, feature: Feature) -> None:
restart_container("lightspeed-stack")
remove_config_backup(context.default_config_backup)

if "RBAC" in feature.tags:
switch_config(context.default_config_backup)
restart_container("lightspeed-stack")
remove_config_backup(context.default_config_backup)

if "Feedback" in feature.tags:
for conversation_id in context.feedback_conversations:
url = f"http://{context.hostname}:{context.port}/v1/conversations/{conversation_id}"
Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/features/info.feature
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Feature: Info tests
{"detail": {"response": "Unable to connect to Llama Stack", "cause": "Connection error."}}
"""

#https://issues.redhat.com/browse/LCORE-1211
@skip
Scenario: Check if tools endpoint is working
Given The system is in default state
When I access REST API endpoint "tools" using HTTP GET method
Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/features/query.feature
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Feature: Query endpoint API tests
| Fragments in LLM response |
| checkout |

#enable on demand
@skip
Scenario: Check if LLM ignores new system prompt in same conversation
Given The system is in default state
And I set the Authorization header to Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva
Expand Down
Loading
Loading