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
1 change: 0 additions & 1 deletion .github/workflows/_system_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
env:
# https://github.com/pytest-dev/pytest/issues/2042
PY_IGNORE_IMPORTMISMATCH: "1"
TILED_SINGLE_USER_API_KEY: unknown # ${TILED_SINGLE_USER_API_KEY}

jobs:
run:
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ jobs:
runs-on: ${{ matrix.runs-on }}
python-version: ${{ matrix.python-version }}

# https://github.com/DiamondLightSource/blueapi/issues/1297
# Temporarily disabled until Tiled release with authz
# system-test:
# uses: ./.github/workflows/_system_test.yml
system-test:
uses: ./.github/workflows/_system_test.yml

container:
needs: test
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ classifiers = [
]
description = "Lightweight bluesky-as-a-service wrapper application. Also usable as a library."
dependencies = [
"tiled[client]>=0.2.0",
"tiled[client]>=0.2.3",
"bluesky[plotting]>=1.14.0", # plotting includes matplotlib, required for BestEffortCallback in run plans
"ophyd-async>=0.13.5",
"aioca",
Expand Down Expand Up @@ -69,6 +69,7 @@ dev = [
"mock",
"jwcrypto",
"deepdiff",
"tiled[minimal-server]>=0.2.3", # For system-test of dls.py
]

[project.scripts]
Expand Down
42 changes: 37 additions & 5 deletions tests/system_tests/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,43 @@ services:
source: ./services/rabbitmq_plugins
target: /etc/rabbitmq/enabled_plugins

tiled:
image: ghcr.io/bluesky/tiled:0.2.0
keycloak:
image: keycloak/keycloak:26.4
environment:
- KC_BOOTSTRAP_ADMIN_PASSWORD=admin
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
- KC_HOSTNAME=http://localhost:8081
command: ["start-dev"]
volumes:
- ./services/keycloak_config/:/mnt/
post_start:
- command: bash /mnt/startup.sh
ports:
- "8407:8000"
- 8081:8080
healthcheck:
test: /opt/keycloak/bin/kcadm.sh config credentials --server http://keycloak:8080 --realm master --user admin --password admin
interval: 5s
timeout: 5s
retries: 10
start_period: 30s

tiled:
image: ghcr.io/bluesky/tiled:0.2.3
network_mode: host
environment:
- TILED_SINGLE_USER_API_KEY=${TILED_SINGLE_USER_API_KEY}
- PYTHONPATH=/deploy/
volumes:
- ./services/tiled_config:/deploy/config
command: ["tiled", "serve", "config", "--host", "0.0.0.0", "--port", "8407"]
depends_on:
keycloak:
condition: service_healthy

opa:
image: openpolicyagent/opa:edge-static-debug
network_mode: host
volumes:
- ./services/tiled_config:/deploy/config
- "./services/opa_config:/mnt"
environment:
- ISSUER=http://localhost:8081/realms/master
entrypoint: "sh /mnt/entrypoint.sh"
6 changes: 5 additions & 1 deletion tests/system_tests/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ numtracker:
url: http://localhost:8406/graphql
tiled:
enabled: true
url: http://localhost:8407/
url: http://localhost:8407/api/v1
oidc:
well_known_url: "http://localhost:8081/realms/master/.well-known/openid-configuration"
client_id: "ixx-cli-blueapi"
client_audience: "blueapi"
26 changes: 26 additions & 0 deletions tests/system_tests/services/keycloak_config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Keycloak Configuration

This Keycloak instance has two clients: `ixx-blueapi` and `ixx-cli-blueapi`.

## ixx-blueapi

A service account client used to obtain access tokens from Keycloak during system tests.

**Key points:**
- Uses a hardcoded fedid for the user `alice`
- In CI, this acts as `alice` to validate permissions
- The hardcoded value tests [authz](https://github.com/DiamondLightSource/authz/) policies

## ixx-cli-blueapi

A client for testing all services with admin privileges. Note that while the production `blueapi-cli` client has no write access, this test instance has write permissions for testing purposes.

## Configuration

Both clients are generated by exporting the realm. For more information, see:
- [Realm import/export](https://www.keycloak.org/server/importExport)
- [Client configuration export](https://www.keycloak.org/securing-apps/client-registration-cli#_retrieving_a_client_configuration)

## Users

A single user is configured with username and password: `admin`
187 changes: 187 additions & 0 deletions tests/system_tests/services/keycloak_config/ixx-blueapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
{
"clientId": "ixx-blueapi",
"name": "Blueapi",
"description": "Service account client used to run automated system tests for Blueapi.",
"rootUrl": "",
"adminUrl": "",
"baseUrl": "",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"secret": "secret",
"redirectUris": [
"/*"
],
"webOrigins": [
"/*"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
"realm_client": "false",
"oidc.ciba.grant.enabled": "false",
"client.secret.creation.time": "1748358661",
"backchannel.logout.session.required": "true",
"standard.token.exchange.enabled": "false",
"frontchannel.logout.session.required": "true",
"oauth2.device.authorization.grant.enabled": "false",
"display.on.consent.screen": "false",
"backchannel.logout.revoke.offline.tokens": "false",
"dpop.bound.access.tokens": "false",
"pkce.code.challenge.method": "",
"login_theme": "",
"consent.screen.text": "",
"frontchannel.logout.url": "",
"use.jwks.url": "false",
"logoUri": "",
"policyUri": "",
"tosUri": "",
"access.token.signed.response.alg": "",
"access.token.header.type.rfc9068": "false",
"id.token.signed.response.alg": "",
"id.token.encrypted.response.alg": "",
"id.token.encrypted.response.enc": "",
"id.token.as.detached.signature": "false",
"user.info.response.signature.alg": "",
"user.info.encrypted.response.alg": "",
"user.info.encrypted.response.enc": "",
"request.object.signature.alg": "any",
"request.object.encryption.alg": "any",
"request.object.encryption.enc": "any",
"request.object.required": "not required",
"authorization.signed.response.alg": "",
"authorization.encrypted.response.alg": "",
"authorization.encrypted.response.enc": "",
"exclude.session.state.from.auth.response": "",
"exclude.issuer.from.auth.response": "",
"use.refresh.tokens": "true",
"client_credentials.use_refresh_token": "false",
"token.response.type.bearer.lower-case": "false",
"standard.token.exchange.enableRefreshRequestedTokenType": "",
"access.token.lifespan": 3600,
"client.session.idle.timeout": "",
"client.session.max.lifespan": "",
"client.offline.session.idle.timeout": "",
"tls.client.certificate.bound.access.tokens": "false",
"require.pushed.authorization.requests": "false",
"client.use.lightweight.access.token.enabled": "false",
"client.introspection.response.allow.jwt.claim.enabled": "false",
"minimum.acr.value": "",
"acr.loa.map": "{}"
},
"authenticationFlowBindingOverrides": {
"browser": "",
"direct_grant": ""
},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"protocolMappers": [
{
"name": "alice",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"consentRequired": false,
"config": {
"introspection.token.claim": "true",
"claim.value": "alice",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"lightweight.claim": "false",
"access.token.claim": "true",
"claim.name": "fedid",
"jsonType.label": "String",
"access.tokenResponse.claim": "false"
}
},
{
"name": "Client Host",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientHost",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientHost",
"jsonType.label": "String"
}
},
{
"name": "Client ID",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "client_id",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "client_id",
"jsonType.label": "String"
}
},
{
"name": "blueapi",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"id.token.claim": "false",
"lightweight.claim": "false",
"introspection.token.claim": "true",
"access.token.claim": "true",
"included.custom.audience": "blueapi",
"userinfo.token.claim": "false"
}
},
{
"name": "Client IP Address",
"protocol": "openid-connect",
"protocolMapper": "oidc-usersessionmodel-note-mapper",
"consentRequired": false,
"config": {
"user.session.note": "clientAddress",
"introspection.token.claim": "true",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "clientAddress",
"jsonType.label": "String"
}
}
],
"defaultClientScopes": [
"service_account",
"web-origins",
"acr",
"profile",
"roles",
"basic",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"organization",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
},
"authorizationServicesEnabled": false
}
Loading