From 3aa02716c80c1ce5b8212dc9ae1d8e5bb3fa3183 Mon Sep 17 00:00:00 2001 From: Russell Haering Date: Wed, 7 Jan 2026 12:09:27 -0800 Subject: [PATCH] Add baton-http connector documentation Add comprehensive documentation for the baton-http connector, covering: - Configuration structure with required version field - All 7 authentication methods (none, basic, bearer, api_key, oauth2_client_credentials, oauth2_password, bearer_dynamic) - Resource type configuration with CEL expressions - Pagination strategies (offset, cursor, page, link, none) - Entitlements, grants, and provisioning - Kubernetes deployment examples - Working examples based on actual connector configs --- baton/baton-http.mdx | 836 +++++++++++++++++++++++++++++++++++++++++++ docs.json | 1 + 2 files changed, 837 insertions(+) create mode 100644 baton/baton-http.mdx diff --git a/baton/baton-http.mdx b/baton/baton-http.mdx new file mode 100644 index 0000000..10f4472 --- /dev/null +++ b/baton/baton-http.mdx @@ -0,0 +1,836 @@ +--- +title: "Build a custom connector with Baton-HTTP" +description: "Build a custom connector to sync users, groups, roles, and other resources from any HTTP/REST API-enabled application." +og:title: "Build a custom connector with Baton-HTTP" +og:description: "Build a custom connector to sync users, groups, roles, and other resources from any HTTP/REST API-enabled application." +sidebarTitle: "Build a connector with Baton-HTTP" +--- + +## Overview + +[Baton-HTTP](https://github.com/ConductorOne/baton-http) is a flexible, configuration-driven connector for [Baton](https://github.com/conductorone/baton) that enables integration with any HTTP-based API. If you have back-office, home-grown, or on-prem applications that expose an HTTP API but don't have a dedicated Baton connector, use Baton-HTTP to bring those apps' access data into ConductorOne. + +This connector allows you to: + +- Sync users, groups, roles, and custom resource types from any HTTP API +- Define custom mappings using YAML configuration with CEL expressions +- Configure authentication methods including OAuth2, API keys, bearer tokens, and basic auth +- Enable provisioning actions for granting and revoking access + +## Configuration overview + +Baton-HTTP is driven by a YAML configuration file that defines: + +- Application metadata +- HTTP API connection details and authentication +- Resource types to sync (users, groups, roles, etc.) +- Entitlements that can be granted to resources +- Grants that define which principals have which entitlements +- Provisioning rules for granting/revoking access + +## Command-line options + +The connector accepts the following command-line flags: + +| Flag | Description | +| :--- | :--- | +| `--config-path` | **(Required)** Path to the YAML configuration file | +| `--validate-config-only` | Validate the configuration file and exit without running the connector | +| `--enable-command-actions` | Enable command actions (shell script execution) | +| `--client-id` | ConductorOne client ID for service mode | +| `--client-secret` | ConductorOne client secret for service mode | + + +Authentication credentials for the target API are configured in the YAML file using environment variable interpolation (e.g., `${API_TOKEN}`), not via command-line flags. + + +## YAML configuration structure + +Every Baton-HTTP configuration file must include these core elements: + +```yaml +version: "1" # Required, must be "1" +app_name: "Your Application" # Required +app_description: "Optional description" + +connect: + base_url: "https://api.example.com/v1" + auth: + type: "bearer" + token: "${API_TOKEN}" + +resource_types: + user: + # Resource configuration... +``` + +### Top-level fields + +| Field | Required | Description | +| :--- | :--- | :--- | +| `version` | Yes | Schema version, must be `"1"` | +| `app_name` | Yes | Application name for this connector | +| `app_description` | No | Description of the application | +| `vars` | No | Global variables available throughout configuration | +| `connect` | Yes | API connection and authentication settings | +| `http` | No | HTTP client settings (timeouts, retries) | +| `resource_types` | Yes | Resource type definitions (minimum 1 required) | +| `error_handling` | No | Global error handling configuration | +| `actions` | No | Custom action definitions | + +## Connection configuration + +The `connect` section defines how to connect to your HTTP API: + +```yaml +connect: + base_url: "https://api.example.com/v1" + + auth: + type: "bearer" + token: "${API_TOKEN}" + + request_defaults: + content_type: "application/json" + headers: + Accept: "application/json" + query_params: + limit: "100" + + pagination: + strategy: "offset" + limit_param: "limit" + offset_param: "offset" + page_size: 25 +``` + +## Authentication methods + +Baton-HTTP supports multiple authentication methods. Configure authentication under `connect.auth`. + +### No authentication + +```yaml +auth: + type: none +``` + +### Bearer token + +Use when the API requires a static bearer token: + +```yaml +auth: + type: bearer + token: "${API_TOKEN}" +``` + +### Basic authentication + +Use for username/password authentication: + +```yaml +auth: + type: basic + username: "${API_USERNAME}" + password: "${API_PASSWORD}" +``` + +### API key + +Use when the API requires an API key in a header: + +```yaml +auth: + type: api_key + api_key: + header: "X-API-Key" # Header name + prefix: "ApiKey" # Optional prefix + key: "${API_KEY}" # The key value +``` + +### OAuth2 client credentials + +Use for APIs requiring OAuth2 client credentials flow: + +```yaml +auth: + type: oauth2_client_credentials + oauth2_client_credentials: + token_url: "https://api.example.com/oauth/token" + # Or use OIDC discovery: + # issuer: "https://login.example.com" + client_id: "${CLIENT_ID}" + client_secret: "${CLIENT_SECRET}" + scope: "read:users read:groups" + token_expiry_padding: 60 # Refresh 60 seconds before expiry +``` + +### OAuth2 password (ROPC) + +Use for OAuth2 Resource Owner Password Credentials flow: + +```yaml +auth: + type: oauth2_password + oauth2_password: + token_url: "https://api.example.com/oauth/token" + client_id: "${CLIENT_ID}" + client_secret: "${CLIENT_SECRET}" + username: "${USERNAME}" + password: "${PASSWORD}" + scope: "api" +``` + +### Bearer dynamic + +Use for APIs that issue tokens via a login endpoint: + +```yaml +auth: + type: bearer_dynamic + bearer_dynamic: + token_url: "https://api.example.com/login" + username: "${USERNAME}" + password: "${PASSWORD}" + token_field: "access_token" # JSON field containing token + expiry_field: "expires_at" # Optional, auto-parses JWT if omitted + token_expiry_padding: 60 +``` + +## Resource type configuration + +Resource types define the entities you want to sync. Each resource type specifies how to list resources and map API responses to ConductorOne resources. + +### Basic structure + +```yaml +resource_types: + user: + name: "User" + description: "User accounts" + traits: + - user + + list: + request: + method: GET + url: /users + response: + items_path: items + mapping_type: resource + resource_mapping: + id: cel:item.id + display_name: cel:item.name + user_traits: + email: cel:item.email + login: cel:item.username + status: 'cel:item.active ? "enabled" : "disabled"' + + skip_entitlements_and_grants: true +``` + +### Resource type fields + +| Field | Required | Description | +| :--- | :--- | :--- | +| `name` | Yes | Human-readable name | +| `description` | No | Description of this resource type | +| `traits` | No | Traits for this type (`user`, `group`, `role`, `app`) | +| `depends_on` | No | Resource types that must be processed first | +| `parent_type` | No | Parent resource type for hierarchical resources | +| `child_types` | No | Child resource types | +| `list` | Yes* | How to list resources (*unless using `static_resources`) | +| `static_resources` | No | Statically defined resources | +| `static_entitlements` | No | Predefined entitlements | +| `entitlements` | No | Dynamic entitlement configuration | +| `grants` | No | Grant discovery configuration | +| `skip_entitlements_and_grants` | No | Skip entitlement/grant processing | + +## Data mapping with CEL expressions + +Baton-HTTP uses [Common Expression Language (CEL)](https://github.com/google/cel-spec) for data mapping. CEL expressions are prefixed with `cel:`. + +### Response mapping + +```yaml +response: + items_path: data.users # Path to array in response + mapping_type: resource # resource, grant, or entitlement + resource_mapping: + id: cel:item.id + display_name: cel:item.firstName + " " + item.lastName + description: cel:item.email + user_traits: + email: cel:item.email + login: cel:item.username + status: 'cel:item.status == "active" ? "enabled" : "disabled"' + profile: + first_name: cel:item.firstName + last_name: cel:item.lastName + department: cel:item.department +``` + +### Common CEL patterns + +```yaml +# Conditional expressions +status: 'cel:item.active ? "enabled" : "disabled"' + +# String concatenation +display_name: cel:item.firstName + " " + item.lastName + +# Null-safe access with has() +parent_id: "cel:has(item.parent.id) ? item.parent.id : null" + +# Array access +primary_email: cel:item.emails[0].address +``` + +## Templating with Go templates + +For URLs and request bodies, use Go templates prefixed with `tmpl:`: + +```yaml +# URL templating +url: tmpl:/groups/{{.resource.id}}/members + +# With parent resource +url: tmpl:/orgs/{{.parent_resource.id}}/teams/{{.resource.id}} + +# Request body templating +body: | + tmpl:{ + "user_id": "{{.principal.id}}", + "role": "member" + } +``` + +### Available template variables + +| Variable | Description | +| :--- | :--- | +| `.resource.id` | Current resource ID | +| `.resource.display_name` | Current resource display name | +| `.parent_resource.id` | Parent resource ID | +| `.principal.id` | Principal ID (for provisioning) | +| `.principal.traits.user.email` | Principal's email | +| `.pre_requests..body` | Response from a pre-request | + +## Pagination + +Configure pagination under `connect.pagination` (global) or per-resource under `list.pagination`. + +### Offset-based pagination + +```yaml +pagination: + strategy: offset + limit_param: limit + offset_param: offset + page_size: 100 + total_path: totalResults # Optional: path to total count +``` + +### Cursor-based pagination + +```yaml +pagination: + strategy: cursor + limit_param: limit + cursor_param: cursor + cursor_path: meta.nextCursor # Path to next cursor in response + page_size: 100 +``` + +### Page-based pagination + +```yaml +pagination: + strategy: page + limit_param: per_page + page_param: page + page_start: 1 # Starting page number + page_size: 100 +``` + +### Link-based pagination + +```yaml +pagination: + strategy: link + next_link_path: links.next # Path to next URL in response +``` + +### No pagination + +```yaml +pagination: + strategy: none +``` + +## Entitlements + +Entitlements define permissions that can be granted to resources. + +### Static entitlements + +Use for predefined entitlements like group membership: + +```yaml +static_entitlements: + - id: member + display_name: cel:resource.display_name + " Member" + description: cel:"Member of " + resource.display_name + slug: member + purpose: assignment # assignment, permission, or role + grantable_to: + - user +``` + +### Dynamic entitlements + +Fetch entitlements from an API: + +```yaml +entitlements: + - request: + url: tmpl:/spaces/{{.resource.id}}/permissions + response: + items_path: items + mapping_type: entitlement + entitlement_mapping: + id: cel:resource.id + "-" + item.operation + display_name: cel:"Can " + item.operation + " on " + resource.display_name + slug: cel:item.operation + purpose: permission + grantable_to: + - user + - group +``` + +## Grants + +Grants define which principals have which entitlements: + +```yaml +grants: + - request: + url: tmpl:/groups/{{.resource.id}}/members + response: + items_path: members + mapping_type: grant + grant_mapping: + principal_id: cel:item.userId + principal_type: user + entitlement_name: member +``` + +### Conditional grants + +Use `skip_if` to filter grants: + +```yaml +grants: + - request: + url: tmpl:/resources/{{.resource.id}}/permissions + response: + items_path: items + skip_if: cel:item.subject.type == "anonymous" + mapping_type: grant + grant_mapping: + principal_id: cel:item.subject.id + principal_type: cel:item.subject.type + entitlement_name: cel:item.permission +``` + +## Provisioning + +Enable provisioning to grant and revoke access through ConductorOne. + +### Grant and revoke configuration + +```yaml +static_entitlements: + - id: member + display_name: "Group Member" + purpose: assignment + grantable_to: + - user + provisioning: + grant: + request: + method: POST + url: tmpl:/groups/{{.resource.id}}/members + body: | + tmpl:{ + "user_id": "{{.principal.id}}" + } + success_condition: cel:response.status_code == 201 + revoke: + request: + method: DELETE + url: tmpl:/groups/{{.resource.id}}/members/{{.principal.id}} + success_condition: cel:response.status_code == 204 +``` + +### Pre-requests + +Use pre-requests when you need data from another API call: + +```yaml +provisioning: + grant: + request: + pre_requests: + user_details: + url: tmpl:/users/{{.principal.id}} + method: GET + method: PUT + url: tmpl:/groups/{{.resource.id}}/members/{{.pre_requests.user_details.body.username}} +``` + +## HTTP client settings + +Configure timeouts and retries under the `http` section: + +```yaml +http: + timeout: 30s + headers: + Accept: "application/json" + retry: + max_attempts: 3 + initial_backoff: 1s + max_backoff: 10s +``` + +## Error handling + +Configure error handling globally or per-request: + +```yaml +error_handling: + error_status_codes: + "429": + action: retry + retry_after: 30s + max_retries: 3 + "404": + action: warn + message: "Resource not found" + "500": + action: retry + retry_after: 5s +``` + +Error actions: `fail` (default), `retry`, `warn`, `ignore` + +## Running the connector + +### Local execution + +```bash +# Run a sync +baton-http --config-path ./config.yaml + +# Validate configuration only +baton-http --config-path ./config.yaml --validate-config-only + +# Connect to ConductorOne (service mode) +baton-http --config-path ./config.yaml \ + --client-id "$C1_CLIENT_ID" \ + --client-secret "$C1_CLIENT_SECRET" +``` + +### Using environment variables + +Set credentials via environment variables referenced in your config: + +```bash +export API_TOKEN="your-api-token" +export API_BASE_URL="https://api.example.com" +baton-http --config-path ./config.yaml +``` + +## Deploying to ConductorOne + +### Step 1: Set up a new connector + + + +In ConductorOne, navigate to **Connectors** > **Add connector**. + + +Search for **Baton** and click **Add**. + + +Choose how to set up the new connector: + - Add the connector to a currently unmanaged app + - Add the connector to a managed app + - Create a new managed app + + +Set the owner for this connector. + + +Click **Next**. + + +In the **Settings** area of the page, click **Edit**. + + +Click **Rotate** to generate a new Client ID and Secret. Save these credentials. + + + +### Step 2: Create Kubernetes configuration + +#### Secret + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: baton-http-secrets +type: Opaque +stringData: + C1_CLIENT_ID: + C1_CLIENT_SECRET: + API_TOKEN: +``` + +#### ConfigMap + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: baton-http-config +data: + config.yaml: | + version: "1" + app_name: My Application + + connect: + base_url: "https://api.example.com/v1" + auth: + type: bearer + token: "${API_TOKEN}" + + resource_types: + user: + name: User + traits: + - user + list: + request: + url: /users + response: + items_path: data + mapping_type: resource + resource_mapping: + id: cel:item.id + display_name: cel:item.name + user_traits: + email: cel:item.email + status: 'cel:item.active ? "enabled" : "disabled"' + skip_entitlements_and_grants: true +``` + +#### Deployment + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: baton-http +spec: + selector: + matchLabels: + app: baton-http + template: + metadata: + labels: + app: baton-http + spec: + containers: + - name: baton-http + image: ghcr.io/conductorone/baton-http:latest + args: + - "--config-path=/config/config.yaml" + envFrom: + - secretRef: + name: baton-http-secrets + volumeMounts: + - name: config + mountPath: /config + volumes: + - name: config + configMap: + name: baton-http-config +``` + +### Step 3: Deploy + +Apply the configuration files to your Kubernetes cluster and verify the connector appears in ConductorOne under **Applications** > **Managed apps**. + +## Example: GitHub integration + +```yaml +version: "1" +app_name: Github +app_description: Github Organization + +connect: + base_url: https://api.github.com + auth: + type: bearer + token: "${GITHUB_TOKEN}" + request_defaults: + headers: + Accept: application/json + query_params: + per_page: 100 + +resource_types: + org: + name: Organization + traits: + - app + child_types: + - user + - team + list: + request: + url: /user/orgs + response: + items_path: items + mapping_type: resource + resource_mapping: + id: cel:item.id + display_name: cel:item.login + + user: + name: User + depends_on: + - org + parent_type: org + traits: + - user + list: + request: + url: tmpl:/orgs/{{.parent_resource.id}}/members + response: + items_path: items + mapping_type: resource + resource_mapping: + id: cel:item.login + display_name: cel:item.login + user_traits: + login: cel:item.login + skip_entitlements_and_grants: true + + team: + name: Team + depends_on: + - org + parent_type: org + traits: + - group + list: + request: + url: tmpl:/orgs/{{.parent_resource.id}}/teams + response: + items_path: items + mapping_type: resource + resource_mapping: + id: cel:item.id + display_name: cel:item.name + group_traits: + profile: + description: cel:item.description + static_entitlements: + - id: member + display_name: "Team Member" + purpose: assignment + grantable_to: + - user + grants: + - request: + url: tmpl:/orgs/{{.parent_resource.id}}/team/{{.resource.id}}/members + response: + items_path: items + mapping_type: grant + grant_mapping: + principal_id: cel:item.login + principal_type: user + entitlement_name: member +``` + +## Example: OAuth2 with provisioning + +```yaml +version: "1" +app_name: ConductorOne +app_description: ConductorOne API Integration + +connect: + base_url: https://${C1_ENDPOINT}/api/v1 + auth: + type: oauth2_client_credentials + oauth2_client_credentials: + token_url: https://${C1_ENDPOINT}/auth/v1/token + client_id: "${C1_CLIENT_ID}" + client_secret: "${C1_CLIENT_SECRET}" + request_defaults: + headers: + Accept: application/json + +resource_types: + user: + name: User + traits: + - user + list: + request: + url: /users + response: + items_path: list + mapping_type: resource + resource_mapping: + id: cel:item.user.id + display_name: cel:item.user.displayName + user_traits: + login: cel:item.user.username + email: cel:item.user.email + skip_entitlements_and_grants: true +``` + +## Troubleshooting + +### Authentication errors + +- Verify credentials are correct and environment variables are set +- Check token hasn't expired +- For OAuth2, verify token URL and scopes + +### Resources not syncing + +- Use `--validate-config-only` to check configuration +- Verify `items_path` matches your API response structure +- Check CEL expressions with sample data + +### Pagination issues + +- Confirm pagination `strategy` matches your API +- Verify parameter names (`limit_param`, `offset_param`, etc.) +- Check `cursor_path` or `next_link_path` for cursor/link pagination + +### Mapping errors + +- Test CEL expressions against sample API responses +- Use `has()` for optional fields to avoid null errors +- Check for typos in field names + +For more information, see the [baton-http repository](https://github.com/ConductorOne/baton-http) and the [docs directory](https://github.com/ConductorOne/baton-http/tree/main/docs) for detailed specifications. diff --git a/docs.json b/docs.json index f9be923..ab71c4b 100644 --- a/docs.json +++ b/docs.json @@ -217,6 +217,7 @@ "group": "Build connectors", "pages": [ "baton/custom", + "baton/baton-http", "baton/baton-scim", "baton/baton-sql" ]