Skip to content
Merged
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
30 changes: 30 additions & 0 deletions doc/getting_started/pyrit_conf.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ Client settings for connecting to or launching a PyRIT backend.
| --- | --- | --- |
| `url` | Backend URL used when `--server-url` is omitted | `http://localhost:8000` |
| `startup_timeout` | Seconds `pyrit_scan start-server` waits for a healthy backend before terminating the spawned process | `120` |
| `auth_mode` | Backend authentication mode: `auto`, `azure_cli`, `device_code`, or `none` | `auto` |

`startup_timeout` must be a finite number greater than zero. The `--startup-timeout` CLI option overrides the configured value for an individual scanner invocation.

Expand All @@ -273,8 +274,37 @@ Set `server: null` to reset all server settings, including values inherited from
server:
url: http://localhost:8000
startup_timeout: 120
auth_mode: auto
```

In `auto` mode, the CLI reads the backend's public `/api/auth/config` endpoint. It sends no
token when authentication is disabled. For an authenticated backend, it uses Entra device-code
login with the exact Microsoft Graph `User.Read` scope. The encrypted persistent token cache
normally prevents a new prompt on each run. A non-interactive process fails instead of waiting
for a prompt.

Use an explicit mode when needed:

```yaml
server:
url: https://copyrit.example.com/
auth_mode: azure_cli
```

`azure_cli` is an explicit compatibility mode. It can send a Microsoft Graph token with
permissions beyond `User.Read` because the Azure CLI application controls the token's granted
permissions. Prefer `auto` or `device_code`. If you accept this behavior, sign in to the
backend's tenant before using `azure_cli`:

```bash
az login --tenant <tenant-id>
pyrit_scan --config-file ./.pyrit_conf list-scenarios
```

Use `device_code` to require the same exact-scope interactive flow as `auto`. Use `none` only
when you intentionally need to suppress authentication discovery. Access tokens are not stored
in `.pyrit_conf`.

## Configuration Precedence

PyRIT uses a 3-layer configuration precedence model. **Later layers override earlier ones:**
Expand Down
25 changes: 25 additions & 0 deletions doc/scanner/0_scanner.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,31 @@ PyRIT provides two command-line interfaces:
pyrit_scan run foundry.red_team_agent --target openai_chat --initializers target --techniques base64
```

### Connecting to CoPyRIT

Point a local configuration file at the remote backend:

```yaml
server:
url: https://copyrit.example.com/
auth_mode: auto
```

Then use the file without changing the default configuration in `~/.pyrit`:

```bash
pyrit_scan --config-file ./.pyrit_conf list-scenarios
```

The CLI reads the server's public authentication configuration. Automatic mode uses an
interactive Entra device code with the exact Microsoft Graph `User.Read` scope and an encrypted
persistent token cache. Use `--auth-mode device_code` to require this flow or `--auth-mode none`
to disable authentication discovery.

`--auth-mode azure_cli` is an explicit compatibility mode. The Azure CLI application can issue
a Graph token with permissions beyond `User.Read`, and the CLI sends that token to the backend.
Prefer automatic device-code authentication.

## Built-in Scenarios

PyRIT ships with scenarios organized into the following families:
Expand Down
9 changes: 9 additions & 0 deletions doc/scanner/2_pyrit_shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,17 @@ pyrit_shell --config-file ./.pyrit_conf

# Set default log level
pyrit_shell --log-level DEBUG

# Connect to an authenticated remote backend
pyrit_shell --config-file ./.pyrit_conf --auth-mode auto
```

Authentication defaults to `auto`. The shell uses exact-scope device-code login and stores the
result in an encrypted persistent token cache. The configuration file can set
`server.auth_mode` to `device_code` or `none` when automatic selection is not appropriate.
`azure_cli` remains an explicit compatibility mode, but its Graph token can contain permissions
beyond `User.Read`.

## Available Commands

Once starting the shell, you will see the list of commands you have access to. Some of them are shown below:
Expand Down
27 changes: 20 additions & 7 deletions infra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ managed identity, security response headers, and no embedded secrets.
## Architecture

```
Users ──→ MSAL PKCE auth ──→ Container App
Users ──→ MSAL PKCE or CLI device-code auth ──→ Container App
Graph-backed authentication
Expand Down Expand Up @@ -57,10 +57,11 @@ Production is opt-in via `deployToProd: true`.
## Security

- **Authentication**: [MSAL](https://learn.microsoft.com/en-us/entra/msal/)
[PKCE](https://oauth.net/2/pkce/) on the frontend (`@azure/msal-browser`) +
[PKCE](https://oauth.net/2/pkce/) on the frontend (`@azure/msal-browser`) and
public-client device-code authentication for the PyRIT CLI +
Microsoft Graph-backed middleware on the backend. The frontend sends a delegated
Graph token, and the backend authenticates it through Graph `/me`. PKCE (public
client) requires no client secrets or certificates.
Graph token, and the backend authenticates it through Graph `/me`. These public-client
flows require no client secrets or certificates.
- **Authorization**: Entra group check via `allowedGroupObjectIds` param. Requires
delegated Graph `User.Read`; the backend calls `/me/checkMemberGroups` and compares
the returned transitive memberships with the configured group IDs. Each security
Expand Down Expand Up @@ -165,7 +166,7 @@ az account show --query tenantId -o tsv
> --spa-redirect-uris "https://$FQDN"
> ```

**Configure delegated Microsoft Graph access** (required):
**Configure delegated Microsoft Graph access and public-client login** (required):

In Azure Portal → App registrations → your app → **API permissions**:

Expand All @@ -186,9 +187,13 @@ Or via CLI:
APP_OBJ_ID=$(az ad app show --id $APP_ID --query id -o tsv)
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/applications/$APP_OBJ_ID" \
--body '{"requiredResourceAccess":[{"resourceAppId":"00000003-0000-0000-c000-000000000000","resourceAccess":[{"id":"e1fe6dd8-ba31-4d61-89e7-88639da4683d","type":"Scope"}]}]}'
--body '{"isFallbackPublicClient":true,"requiredResourceAccess":[{"resourceAppId":"00000003-0000-0000-c000-000000000000","resourceAccess":[{"id":"e1fe6dd8-ba31-4d61-89e7-88639da4683d","type":"Scope"}]}]}'
```

`isFallbackPublicClient` enables device-code login for `pyrit_scan` and `pyrit_shell`. In the
Azure Portal, the equivalent setting is **Authentication → Advanced settings → Allow public
client flows → Yes**.

### 3. Entra security groups (required for group-based authorization)

Create one or more security groups for authorized users. Multiple groups can be
Expand Down Expand Up @@ -350,14 +355,22 @@ az deployment group create \

## Post-Deployment

1. **Set SPA redirect URI** on the app registration (requires the FQDN from deploy output):
1. **Configure browser and CLI public-client authentication** on the app registration:
```bash
FQDN=$(az deployment group show -g <rg> -n main \
--query properties.outputs.appFqdn.value -o tsv)
az ad app update --id <entraClientId> \
--spa-redirect-uris "https://$FQDN"

APP_OBJ_ID=$(az ad app show --id <entraClientId> --query id -o tsv)
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/applications/$APP_OBJ_ID" \
--body '{"isFallbackPublicClient":true}'
```

For an existing deployment, run only the `APP_OBJ_ID` and `az rest` commands once. Do not
rerun `infra/deploy_instance.py`; its resource creation steps are not idempotent.

2. **Grant managed identity RBAC** (required — the Bicep template does **not** create
role assignments; the app will fail to start without AcrPull):
```bash
Expand Down
11 changes: 7 additions & 4 deletions infra/deploy_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
7. Managed identity + RBAC role assignments (AcrPull, Storage Blob Data Contributor)
7b. AOAI RBAC (optional — Cognitive Services OpenAI User on specified resources)
8. Bicep deployment (Container App, networking, logging)
9. Post-deploy: SPA redirect URI
9. Post-deploy: SPA redirect URI + public-client device-code flow

Usage:
python infra/deploy_instance.py \\
Expand Down Expand Up @@ -853,15 +853,18 @@ def post_deploy(
fqdn: str,
) -> None:
"""
Run post-deployment steps: SPA redirect URI.
Run post-deployment steps for browser and CLI authentication.

Args:
app_object_id (str): The Entra app registration object ID (for Graph API).
fqdn (str): The deployed app FQDN.
"""
# Set SPA redirect URI via Graph REST API (more portable than --spa-redirect-uris flag)
# Keep browser PKCE and device-code clients on the same public app registration.
logger.info("Setting SPA redirect URI: https://%s", fqdn)
spa_body = {"spa": {"redirectUris": [f"https://{fqdn}"]}}
spa_body = {
"spa": {"redirectUris": [f"https://{fqdn}"]},
"isFallbackPublicClient": True,
}
run_az(
args=[
"rest",
Expand Down
19 changes: 14 additions & 5 deletions pyrit/backend/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
from fastapi import APIRouter

router = APIRouter()
_GRAPH_SCOPES = ["https://graph.microsoft.com/User.Read"]


@router.get("/auth/config")
async def get_auth_config_async() -> dict[str, str]:
async def get_auth_config_async() -> dict[str, str | bool | list[str]]:
"""
Return Entra ID configuration for the frontend MSAL client.

Expand All @@ -25,10 +26,18 @@ async def get_auth_config_async() -> dict[str, str]:
are included so the frontend can show appropriate error messages.

Returns:
dict: Auth configuration with clientId, tenantId, allowedGroupIds.
dict: Auth configuration with enabled state, clientId, tenantId,
allowedGroupIds, and delegated Microsoft Graph scopes.
"""
client_id = os.getenv("ENTRA_CLIENT_ID", "").strip()
tenant_id = os.getenv("ENTRA_TENANT_ID", "").strip()
allowed_group_ids = os.getenv("ENTRA_ALLOWED_GROUP_IDS", "").strip()
enabled = bool(client_id and tenant_id and allowed_group_ids)

return {
"clientId": os.getenv("ENTRA_CLIENT_ID", ""),
"tenantId": os.getenv("ENTRA_TENANT_ID", ""),
"allowedGroupIds": os.getenv("ENTRA_ALLOWED_GROUP_IDS", ""),
"enabled": enabled,
"clientId": client_id,
"tenantId": tenant_id,
"allowedGroupIds": allowed_group_ids,
"scopes": list(_GRAPH_SCOPES) if enabled else [],
}
Loading